2010-04-26 4 views
6

(나는 -O2와 GCC를 사용하고 있습니다.)복사 생성자가 여기에서 생략 된 이유는 무엇입니까?

bar의 사본 필드의 값을 액세스하는 어떤 부작용이 없기 때문에 이것은, 복사 생성자를 생략하다 할 수있는 직접적인 기회처럼 보인다 foo; 복사 생성자 meep meep!이라는 출력을 얻었 기 때문에이라는 이름으로 호출됩니다. a) 귀하는 수정하지 않고 필드 값을 복사하는 보장은 없습니다, 당신의 복사 생성자는 부작용이 있기 때문에 b)는() 메시지를 출력하기 때문에

#include <iostream> 

struct foo { 
    foo(): a(5) { } 
    foo(const foo& f): a(f.a) { std::cout << "meep meep!\n"; } 
    int a; 
}; 

struct bar { 
    foo F() const { return f; } 
    foo f; 
}; 

int main() 
{ 
    bar b; 
    int a = b.F().a; 
    return 0; 
} 
+0

원본 인스턴스의 필드 값을 단순히 반환하는 대신 복사 생성자가 호출되는 이유는 무엇입니까? –

+1

그것이 누락되지 않았 음을 어떻게 알 수 있습니까? 그리고 왜 그랬어야한다고 생각하니? 이 두 가지 쿼리와 관련하여 질문을 수정하십시오. –

+0

@Michael, 맞습니다. –

답변

11

그 것이다 12.8/15에 설명 된 복사의 ctor를 생략 두 법의 경우도 : 자동 변수 함수에서 반환

반환 값 최적화 (및 반환 값이 자동의 복사 반환 값에서 자동으로 자동 생성함으로써 생략 됨) - nope. f은 자동 변수가 아닙니다.

임시 이니셜 라이저 (임시가 객체에 복사되고 임시를 구성하고 복사하는 대신 임시 값이 대상에 직접 생성됩니다) - 아니요 f도 임시가 아닙니다. b.F()은 임시이지만 아무데도 복사되지 않으며 데이터 회원 만 액세스 할 수 있으므로 F()에서 벗어날 때까지는 아무 것도 없습니다.

의 반환 값으로 f을 복사하는 것이 프로그램의 관찰 가능한 동작에 영향을 미치지 않으므로이 표준은이 프로그램을 무시하는 것을 금지합니다. 인쇄물을 관찰 할 수없는 활동으로 대체하고 어셈블리를 검사 한 경우이 복사본 생성자가 최적화되어 있는지 확인할 수 있습니다. 그러나 그것은 복사 생성자 엘레멘트 규칙에 의한 것이 아니라 "as-if"규칙에 따른다.

+1

흥미 롭습니다. 감사합니다. 표준이 왜이 경우를 허용하지 않는가? –

+0

이전 함수를 생략하지 않는 것과 같은 이유 때문에'std :: cout <<'에 대한 호출을 포함합니다. 큰 문제는 왜 표준이 프로그램의 관찰 가능한 행동을 변화시키는 "최적화"를 허용 하는가? 그 대답은, 일시와 반환 가치의 우스운 사슬을 피할 필요가 있고, 아무도 그 두 경우에서 수행되는 복사에 의존하기를 원하지 않는다고 생각됩니다. 케이스에 복사하지 않으려면'const foo &'를 반환 할 수 있습니다. 사본 ctor elision의 두 가지 합법적 인 경우 사본을 그렇게 피할 수 없습니다. –

+0

그래서 필자는 Elision의 사례가 귀하의 사례를 다루기 위해 추가 된 이유가 의심 스럽습니다. 귀하가 직접 할 수있는 것이고 필요가 없으면 행동을 위반하는 특별한 경우가 좋지 않습니다. 그것은 단지 추측입니다. –

1

는 복사 생성자가 호출된다.

+3

부작용이있는 복사 생성자를 생략 할 수 있습니다. 결론 - 그런 복사 생성자를 작성하지 마십시오. –

1

복사 제거에 대해 생각해 볼 수있는 더 좋은 방법은 임시 개체입니다. 그것이 표준이 그것을 어떻게 설명하는지입니다. 임시는 파기 직전에 영구 객체에 복사 된 경우 영구 객체에 "접히는"것이 허용됩니다.

여기서 함수 return에 임시 객체를 생성합니다. 실제로 어떤 일에도 참여하지 않으므로 건너 뛰기를 원합니다. 그러나 당신은

b.F().a = 5; 

복사가 생략 된 경우

을했던, 당신은 당신이 아닌 참조를 통해 b을 수정 한 것, 원래 객체에 운영합니다.

+0

그러나 lvalue로 사용되지 않으면 컴파일러는 복사본을 삭제할 수 있습니다. –

+0

@Jesse : 예제에서는 좌변 값으로 사용되지 않습니다. 그것은'.' 연산자의 왼쪽으로 사용됩니다. – Potatoswatter

+0

@Potatoswatter -하지만'.' 연산자의 결과는 좌변 값으로 사용됩니다. 나는 C++ 용어에 대해 100 % 확신하지 못하기 때문에 아마도 lvalue는 올바른 단어가 아니지만 내가 찾고있는 개념은 전 이적이어야합니다. –

2

복사 elision은 사본이 실제로 필요하지 않은 경우에만 발생합니다. 특히, 함수의 실행 기간 동안 존재하는 하나의 객체 (A라고 함)와 첫 번째 객체에서 복사 된 두 번째 객체 (B라고 함)가 있고 그 후에 즉시 , A는 파괴 될 것입니다 (즉, 기능이 종료 될 때).

이 매우 특정한 경우에 표준은 컴파일러가 A와 B를 동일한 개체를 참조하는 두 가지 분리 된 방법으로 합치도록 허용합니다.A가 만들어 지도록 요구하는 대신 B가 A로부터 복사 된 다음 A가 파괴되면 A와 B는 동일한 객체를 참조하는 두 가지 방법으로 간주 될 수 있으므로 (하나의) 객체는 A, 함수 반환이 B로 시작되기 시작한 후에도 복사 생성자에 부작용이 있어도 A에서 B를 만드는 복사본은 계속 건너 뛸 수 있습니다. 또한이 경우 A (B와는 별개의 객체)가 파괴되지 않습니다. 예를 들어 dtor에도 부작용이있는 경우이를 생략 할 수도 있습니다.

코드가 패턴에 맞지 않습니다. 첫 번째 개체가 두 번째 개체를 초기화하는 데 사용 된 직후에 중단되지 않습니다. F()이 반환 된 후 두 개의 개체가 있습니다. 즉, [Named] 반환 값 최적화 (일명 복사 제거)는 단순히 적용되지 않습니다. 복사 생략이 적용됩니다

데모 코드 : 최적화가 켜져와

#include <iostream> 

struct foo { 
    foo(): a(5) { } 
    foo(const foo& f): a(f.a) { std::cout << "meep meep!\n"; } 
    int a; 
}; 

int F() { 
    // RVO 
    std::cout << "F\n"; 
    return foo(); 
} 

int G() { 
    // NRVO 
    std::cout << "G\n"; 
    foo x; 
    return x; 
} 

int main() { 
    foo a = F(); 
    foo b = G(); 
    return 0; 
} 

MS VC 모두 ++와 g ++ 최적화 멀리 모두이 코드에서 ctors을 복사합니다. g ++은 최적화가 꺼져 있어도 둘 다 최적화합니다. 최적화가 해제 된 상태에서 VC++는 익명 반환을 최적화하지만 명명 된 반환에 대해 복사 매개 변수를 사용합니다.

관련 문제