Copy elision
특정 조건이 충족될 때, 동일한 타입(상수성/휘발성 한정자는 무시)의 소스 객체로부터 클래스 객체의 생성은, 해당 객체에 대해 선택된 생성자 및/또는 소멸자가 부수 효과를 가지더라도 생략될 수 있습니다. 이러한 객체 생성 생략을 copy elision 이라고 합니다.
목차 |
설명
복사 생략(copy elision)은 다음과 같은 상황에서 허용됩니다(여러 복사를 제거하기 위해 결합될 수 있음):
- 클래스 반환 타입을 가진 함수의 return 문 에서, 피연산자가 자동 저장 기간을 가진 비휘발성 객체 obj 의 이름이고 (함수 매개변수나 핸들러 매개변수 가 아닌 경우), 결과 객체의 복사 초기화 는 obj 를 함수 호출의 결과 객체에 직접 생성함으로써 생략될 수 있습니다. 복사 생략의 이러한 변형은 명명된 반환 값 최적화 (NRVO)로 알려져 있습니다.
|
(C++17까지) |
| (C++11부터) | |
|
(C++20부터) |
복사 생략이 발생할 때, 구현체는 생략된 초기화의 소스와 대상을 단순히 동일한 객체를 참조하는 두 가지 다른 방식으로 취급합니다.
|
최적화 없이 두 객체가 파괴되었을 시점 중 더 늦은 시점에 파괴가 발생합니다. |
(until C++11) |
|
선택된 생성자의 첫 번째 매개변수가 객체 타입에 대한 rvalue reference인 경우, 해당 객체의 파괴는 대상 객체가 파괴되었을 시점에 발생합니다. 그렇지 않은 경우, 최적화 없이 두 객체가 파괴되었을 시점 중 더 늦은 시점에 파괴가 발생합니다. |
(since C++11) |
Prvalue 의미론 ("보장된 복사 생략")C++17부터 prvalue는 필요할 때까지 구체화되지 않으며, 최종 목적지의 저장 공간에 직접 생성됩니다. 이는 때로는 언어 구문이 시각적으로 복사/이동을 암시하는 경우(예: 복사 초기화 )에도 복사/이동이 수행되지 않음을 의미합니다 — 즉, 해당 타입이 접근 가능한 복사/이동 생성자를 전혀 가질 필요가 없음을 의미합니다. 예시는 다음과 같습니다:
T f() { return U(); // U 타입의 임시 객체를 생성한 다음, // 해당 임시 객체로부터 반환될 T를 초기화합니다 } T g() { return T(); // 반환될 T를 직접 생성합니다; 이동 없음 }
T x = T(T(f())); // x는 f()의 결과로 직접 초기화됩니다; 이동 없음
struct C { /* ... */ }; C f(); struct D; D g(); struct D : C { D() : C(f()) {} // 기본 클래스 하위 객체를 초기화할 때는 생략되지 않음 D(int) : D(g()) {} // 초기화되는 D 객체가 다른 클래스의 // 기본 클래스 하위 객체일 수 있으므로 생략되지 않음 }; 참고: 이 규칙은 최적화를 명시하지 않으며, 표준은 이를 공식적으로 "복사 생략"으로 기술하지 않습니다(생략되는 것이 없기 때문입니다). 대신 C++17 핵심 언어 명세에서 prvalues 와 임시 객체 에 대한 설명은 이전 C++ 개정판과 근본적으로 다릅니다: 더 이상 복사/이동할 임시 객체가 존재하지 않습니다. C++17 메커니즘을 설명하는 또 다른 방법은 "비구체화 값 전달" 또는 "지연된 임시 객체 구체화"입니다: prvalues는 임시 객체를 구체화하지 않고 반환되고 사용됩니다. |
(C++17부터) |
참고 사항
복사 생략(copy elision)은 관찰 가능한 부수 효과(side-effects)를 변경할 수 있는 (C++14까지) 관찰 가능한 부수 효과를 변경할 수 있는 두 가지 허용된 최적화 형태 중 하나이며, 할당 생략 및 확장(allocation elision and extension) 과 함께 사용됩니다, (C++14 이후) . 일부 컴파일러는 허용되는 모든 상황에서 복사 생략을 수행하지 않기 때문에(예: 디버그 모드), 복사/이동 생성자 및 소멸자의 부수 효과에 의존하는 프로그램은 이식성이 없습니다.
|
return 문이나 throw 표현식에서, 컴파일러가 copy elision을 수행할 수 없지만 copy elision 조건이 충족되거나, 소스가 함수 매개변수인 경우를 제외하면 조건이 충족될 때, 컴파일러는 소스 피연산자가 lvalue로 지정된 경우에도 move constructor 사용을 시도합니다 (until C++23) 소스 피연산자는 rvalue로 처리됩니다 (since C++23) ; 자세한 내용은 return statement 를 참조하십시오. constant expression 과 constant initialization 에서는 copy elision이 절대 수행되지 않습니다. struct A { void* p; constexpr A() : p(this) {} A(const A&); // Disable trivial copyability }; constexpr A a; // OK: a.p points to a constexpr A f() { A x; return x; } constexpr A b = f(); // error: b.p would be dangling and point to the x inside f constexpr A c = A(); // (until C++17) error: c.p would be dangling and point to a temporary // (since C++17) OK: c.p points to c; no temporary is involved |
(since C++11) |
| 기능 테스트 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__cpp_guaranteed_copy_elision
|
201606L
|
(C++17) | 단순화된 값 범주 를 통한 보장된 복사 생략 |
예제
#include <iostream> struct Noisy { Noisy() { std::cout << "constructed at " << this << '\n'; } Noisy(const Noisy&) { std::cout << "copy-constructed\n"; } Noisy(Noisy&&) { std::cout << "move-constructed\n"; } ~Noisy() { std::cout << "destructed at " << this << '\n'; } }; Noisy f() { Noisy v = Noisy(); // (C++17 이전) 임시 객체로부터 v를 초기화하는 copy elision; // 이동 생성자가 호출될 수 있음 // (C++17 이후) "guaranteed copy elision" return v; // v에서 결과 객체로의 copy elision ("NRVO"); // 이동 생성자가 호출될 수 있음 } void g(Noisy arg) { std::cout << "&arg = " << &arg << '\n'; } int main() { Noisy v = f(); // (C++17 이전) f()의 결과로부터 v를 초기화하는 copy elision // (C++17 이후) "guaranteed copy elision" std::cout << "&v = " << &v << '\n'; g(f()); // (C++17 이전) f()의 결과로부터 arg를 초기화하는 copy elision // (C++17 이후) "guaranteed copy elision" }
가능한 출력:
constructed at 0x7fffd635fd4e &v = 0x7fffd635fd4e constructed at 0x7fffd635fd4f &arg = 0x7fffd635fd4f destructed at 0x7fffd635fd4f destructed at 0x7fffd635fd4e
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 1967 | C++11 |
이동 생성자를 사용하여 복사 생략이 수행될 때,
이동된 객체의 수명이 여전히 고려됨 |
고려되지 않음 |
| CWG 2426 | C++17 | prvalue를 반환할 때 소멸자가 필요하지 않았음 | 소멸자가 잠재적으로 호출됨 |
| CWG 2930 | C++98 |
복사(/이동) 연산만 생략 가능했으나,
비복사(/이동) 생성자가 복사 초기화로 선택될 수 있음 |
관련 복사 초기화의
모든 객체 생성을 생략 |