2013-10-09 4 views
35

많은 경우에 함수에서 로컬을 반환 할 때 RVO가 실행됩니다. 그러나 std::move을 명시 적으로 사용하면 RVO가 발생하지 않을 때 적어도 RVO가 가능한 경우 계속 적용됩니다. 그러나 이것이 그렇지 않은 것으로 보입니다. std :: move가 RVO를 막는 이유는 무엇입니까?

#include "iostream" 

class HeavyWeight 
{ 
public: 
    HeavyWeight() 
    { 
     std::cout << "ctor" << std::endl; 
    } 

    HeavyWeight(const HeavyWeight& other) 
    { 
     std::cout << "copy" << std::endl; 
    } 

    HeavyWeight(HeavyWeight&& other) 
    { 
     std::cout << "move" << std::endl; 
    } 
}; 

HeavyWeight MakeHeavy() 
{ 
    HeavyWeight heavy; 
    return heavy; 
} 

int main() 
{ 
    auto heavy = MakeHeavy(); 
    return 0; 
} 

나는 VC++ (11)와 GCC 4.71, 디버그 및 릴리스 ( -O2) 설정이 코드를 테스트했다. 복사본 매개 변수가 호출되지 않습니다. 이동 ctor는 VC++ 11에 의해서만 디버그 구성에서 호출됩니다. 사실, 모든 이들 컴파일러와 함께 잘 될 것 같습니다,하지만 내 지식에, RVO 선택 사항입니다.

그러나, 나는 명시 적으로 move를 사용하는 경우 :

HeavyWeight MakeHeavy() 
{ 
    HeavyWeight heavy; 
    return std::move(heavy); 
} 

가 이동 ctor에 항상

라고합니다. 그래서 그것을 "안전하게"만들려고하면 악화됩니다.

내 질문 :
- 왜 std::move은 RVO를 방지합니까?
- RVO에 의존하는 것이 "최선을 기원합니다"보다 나은 이유는 무엇이며 언제 명시 적으로 std::move을 사용해야합니까? 즉, 어떻게 RVO가 적용되지 않으면 컴파일러 최적화가 작업을 수행하고 여전히 강제로 이동하도록 할 수 있습니까?

+4

요즘 사람들은 왜 요즘 "최고를 향한 희망"에 대해 이야기합니까? 어떤 종류의 컴파일러가 C++ 11을 지원하지만 RVO를 제대로 지원하지 못합니까? –

+1

복사 elion (RVO의 메커니즘)은 특정 엄격한 조건에서만 허용됩니다. 'std :: move'를 쓰면 이러한 조건이 충족되지 않습니다. –

+2

@KerrekSB 그리고 이러한 상태가 std :: move로 방지되는 이유는 무엇입니까? – cdoubleplusgood

답변

25

사건 표준 (버전 N3690)의 섹션 12.8 §31 발견된다 :

특정 조건을 충족하는

, 구현이 복사/이동을 생략 할 수있다 복사/이동 작업을 위해 선택된 생성자 및/또는 객체의 소멸자가 부작용을 가지고 있더라도 클래스 객체의 생성. 이 경우 구현은 생략 된 복사/이동 작업의 소스와 대상을 동일한 객체를 참조하는 두 가지 다른 방법으로 취급하며 해당 객체의 파괴는 두 객체가 있었던 시간의 후반에 발생합니다 최적화없이 파괴되었습니다. 복사 생략라는 복사/이동 작업이 생략는, (여러 복사본을 제거하기 위해 결합 될 수있다) 다음과 같은 경우에 허용된다 : 클래스 반환 값의 형태와 기능에 return 문에

  • , 표현식이 함수 반환 유형과 동일한 cv-unqualified 유형을 가진 비 휘발성 자동 객체 (함수 또는 catch 절 매개 변수 제외)의 이름 인 경우 자동 객체를 구성하여 복사/이동 작업을 생략 할 수 있습니다 함수의 반환 값에 직접 입력
  • [...]
  • (12.2)에 바인드 된 클래스는 동일한 cv-unqualified 유형을 가진 클래스 객체로 복사/이동 될 것이므로 생략 된 사본/이동 대상에 직접 임시 객체를 구성하여 복사/이동 작업을 생략 할 수 있습니다.
  • [...]

(내가 왼쪽으로 두 경우가 던지는 내가 최적화를위한 덜 중요하다고 생각 예외 객체를 잡는 경우를 참조하십시오.)

는 따라서 return 문 복사 생략에서만 발생할 수 있습니다, 식이 인 경우 로컬 변수의 이름입니다.std::move(var)으로 작성하면 더 이상 변수의 이름이 아닙니다. 따라서 컴파일러는 표준을 준수해야하는 경우 이동을 삭제할 수 없습니다.

Stephan T. Lavavej가 Going Native 2013에 대해 이야기하고 정확하게 상황을 설명하고 피해야하는 이유는 여기 std::move()입니다. 분 38:04에 시청 시작. 기본적으로 반환 유형의 지역 변수를 반환하면 대개 rvalue로 처리되므로 기본적으로 이동이 가능합니다.

+0

이것은 내가 바라는 대답이다. 상황에 만족스럽지 않지만 잘 이해합니다. – cdoubleplusgood

+5

'return std :: move'를 생략 할 수 있도록 수정해야합니다. 함수의 참조 반환 값이 함수의 특정 참조 입력 값과 동일하다는 것을 C++에 알리면 몇 가지 흥미로운 결과가 나타날 수 있습니다. (평범하지 않은 표현에서 나온 Elision, 임시 입력 인수를 임시로 반환하지 않고 함수의 수명 연장, 2 : 두 번째는 IMHO가 더 중요 함). – Yakk

+0

예, 저는 이것을 용서할 수 없다는 합리적인 것을 이해하지 못합니다. 컴파일러 작성자가 더 많은 시간을 할 수 있도록하기가 더 어렵습니다. – Adrian

13

어떻게 컴파일러 최적화 작업을 수행하고 RVO가 적용되지 않으면 이동을 적용 할 수 있습니까? 이처럼

: 이동에 수익을 변형

HeavyWeight MakeHeavy() 
{ 
    HeavyWeight heavy; 
    return heavy; 
} 

는 필수입니다. 복사 및 이동 생략이 허용

+0

그래서 지역을 돌려주는 것은 최악의 경우에 움직이는 것이 보장되며 RVO가 최선의 경우에 적용됩니까? – cdoubleplusgood

+2

@cdoubleplusgood : 예. – Xeo

+0

''return std :: move';에 grep'ping을 할 필요가 있다고 생각합니다.) – goji