2013-07-02 3 views
8

함수의 반환 값을 복사하거나 이동하는 호출자 또는 호출 수신자입니까? 나는이함수의 반환 값은 누구에게 복사합니까?

template <typename T> 
class queue 
{ 
    std::deque<T> d; 
public: 
    // ... // 
    T pop() 
    { 
     // Creates a variable whose destructor removes the first 
     // element of the queue if no exception is thrown. 
     auto guard = ScopeSuccessGuard([=]{ d.pop_front(); }); 
     return d.front(); 
    } 
} 

처럼 큐의 팝업() 함수를 구현하려는 경우 예를 들어, 내 범위 가드의 소멸자는 전면 요소를 복사 한 후라고?

편집 : 후속 질문 : 라인

auto item = q.pop(); 

지금 강하게 예외 안전 할 것인가?

+1

예, 자동 저장 기간 (예 :'가드 '와 같은)이있는 변수는 반환 지침 (및 구문/복사본) 이후에 삭제됩니다. 점프 지침 및 선언에서 표준 부품 6.6 및 6.7을 확인할 수 있습니다. 어쩌면 더 쉽게 더미 오브젝트로 파괴를 시도해 볼 수 있습니다 ^^ – lip

답변

9

로컬 변수가 범위를 벗어나기 전에 반환 값이 복사됩니다. 복사/이동은 임시 위치 (스택 또는 레지스터) 또는 호출자 자신의 버퍼 또는 기본 레지스터에 직접 적용될 수 있습니다 - 최적화/인라인 문제입니다.

임시 위치와 관련하여 컴파일러는 호출자와 호출 수신자간에 작업 단위를 정렬해야하며 반환 값 (물론 함수 매개 변수)에 대해 여러 가지 OS/2 진 오브젝트/실행 가능 형식 특정 규칙이 있습니다. 하나의 컴파일러로 컴파일 된 라이브러리/객체는 일반적으로 다른 컴파일러와 함께 사용할 수 있습니다.

라인은 ...

auto item = q.pop(); 

은 ... 강력 예외 안전 할 것인가? 임시 위치 함수의 리턴 값이 후 다시 호출자 버퍼에 복사되는 반환된다

pop_front() 수없는 throw 가정 흥미로운 경우이다. 당신이 그것에 대해 적절하게 보호하지 못했던 것 같습니다. Elision (호출자의 결과 버퍼/레지스터에서 반환 값을 직접 작성하는 호출 수신자)은 허용되지만 필수는 아닙니다.

이 문제를 탐구하기 위해, 나는 다음과 같은 코드를 작성했습니다 :

#include <iostream> 

struct X 
{ 
    X() { std::cout << "X::X(this " << (void*)this << ")\n"; } 
    X(const X& rhs) { std::cout << "X::X(const X&, " << (void*)&rhs 
           << ", this " << (void*)this << ")\n"; } 
    ~X() { std::cout << "X::~X(this " << (void*)this << ")\n"; } 

    X& operator=(const X& rhs) 
    { std::cout << "X::operator=(const X& " << (void*)&rhs 
       << ", this " << (void*)this << ")\n"; return *this; } 
}; 

struct Y 
{ 
    Y() { std::cout << "Y::Y(this " << (void*)this << ")\n"; } 
    ~Y() { std::cout << "Y::~Y(this " << (void*)this << ")\n"; } 
}; 

X f() 
{ 
    Y y; 
    std::cout << "f() creating an X...\n"; 
    X x; 
    std::cout << "f() return x...\n"; 
    return x; 
}; 

int main() 
{ 
    std::cout << "creating X in main...\n"; 
    X x; 
    std::cout << "x = f(); main...\n"; 
    x = f(); 
} 

g++ -fno-elide-constructors로 컴파일을, (추가 의견) 내 출력했다 :

creating X in main... 
X::X(this 0x22cd50) 
x = f(); main... 
Y::Y(this 0x22cc90) 
f() creating an X... 
X::X(this 0x22cc80) 
f() return x... 
X::X(const X&, 0x22cc80, this 0x22cd40) // copy-construct temporary 
X::~X(this 0x22cc80) // f-local x leaves scope 
Y::~Y(this 0x22cc90) 
X::operator=(const X& 0x22cd40, this 0x22cd50) // from temporary to main's x 
X::~X(this 0x22cd40) 
X::~X(this 0x22cd50) 

분명히 할당이 f() 후 발생 왼쪽 범위 : 스코프 가드 (여기에서는 Y로 표시)가 파괴 된 이후에 예외가 발생합니다.

메인에 X x = f(); 또는 X x(f());이 포함되어있는 경우 동일한 종류가 발생합니다. 단, f() 로컬 변수가 파기 된 후에 호출되는 복사 생성자는 예외입니다.

(나는 하나의 컴파일러의 동작이 표준에 의해 작동하도록 요구되는지 여부를 판단하기에는 좋지 않은 근거이지만, 다른 방법으로는 훨씬 더 안정적이라고 생각합니다. 컴파일러가 고장난 경우, 비교적 희귀 한 - 또는 표준은 그것을 필요로하지 않습니다. 여기에서, 컴파일러의 행동은 방금 표준 요구 사항에 대한 내 인상에 일화 가중치를 추가하는 데 사용됩니다.) 호기심에 대한

가로장 설치 등등 세부 정보 :하지 const 참조 임시의 수명을 연장, 그것은 일반적으로 한 방향으로 만 호출 할 수있는 코드가 유용하지만 안전 할 수있는 일이 const X& x = f();이다, 그러나 표준 에 임시 복사본이있는 임시 사본을 임시로 추가해야한다고 확신 할 수는 없습니다. 조금이라도 가치가있다면 - 내 프로그램에서 "효과가있다"는 것이고 흥미롭게도 임시 값은 반환 값을 추출 할 때 사용되는 동일한 스택 위치를 차지하므로 f() 코드가 효과적으로 추출되어 -f-no-elide-constructors 옵션이 너무 많이 비활성화되지는 않는다 비관주의를 추가하는 방식에서 벗어나는 최적화 : 함수를 호출하기 전에 임시 스택 공간을 남겨두고 함수를 호출하기 위해 여분의 코드를 추가하고 임시 포인터를 제거한 다음 스택 포인터를 다시 조정하십시오.

6

반환 값의 복사본은 호출 수신자에 의해 수행되고, 소멸자가 호출되기 전에 만들어 져야합니다. 그렇지 않으면 로컬로 생성 된 변수의 값/내용을 반환 할 수 없기 때문입니다.

여기에 표준의 관련 부분이다 : 12.4 절, 포인트 11 (소멸자)

소멸자가 자동 ​​저장 기간이 구성 개체에 대한 암시

  • 을 호출 (3.7.3) 때 블록되는 객체가 생성된다 종료 (6.7)

내가 어디 SA가 장소를 찾기 위해 노력했다 이드는 "그 반환이 파괴 이전에 일어난다."그러나 나는 분명히 그것을 말하지 않는다.