Namespaces
Variants

Copy elision

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

특정 조건이 충족될 때, 동일한 타입(상수성/휘발성 한정자는 무시)의 소스 객체로부터 클래스 객체의 생성은, 해당 객체에 대해 선택된 생성자 및/또는 소멸자가 부수 효과를 가지더라도 생략될 수 있습니다. 이러한 객체 생성 생략을 copy elision 이라고 합니다.

목차

설명

복사 생략(copy elision)은 다음과 같은 상황에서 허용됩니다(여러 복사를 제거하기 위해 결합될 수 있음):

  • 클래스 반환 타입을 가진 함수의 return 에서, 피연산자가 자동 저장 기간을 가진 비휘발성 객체 obj 의 이름이고 (함수 매개변수나 핸들러 매개변수 가 아닌 경우), 결과 객체의 복사 초기화 obj 를 함수 호출의 결과 객체에 직접 생성함으로써 생략될 수 있습니다. 복사 생략의 이러한 변형은 명명된 반환 값 최적화 (NRVO)로 알려져 있습니다.
  • 클래스 객체 target 이 참조에 바인딩되지 않은 임시 클래스 객체 obj 로 복사 초기화될 때, obj 를 직접 target 에 생성함으로써 복사 초기화를 생략할 수 있습니다. 이러한 복사 생략 변형은 무명 반환 값 최적화 (URVO)로 알려져 있습니다. C++17부터 URVO는 필수적이며 더 이상 복사 생략의 한 형태로 간주되지 않습니다. 아래를 참조하십시오.
(C++17까지)
  • throw 표현식 에서 피연산자가 가장 안쪽의 try 블록 (존재하는 경우)을 포함하지 않는 범위 에 속하는 자동 저장 기간을 가진 비휘발성 객체 obj (함수 매개변수나 핸들러 매개변수 제외)의 이름일 때, obj 를 예외 객체에 직접 생성함으로써 예외 객체의 복사 초기화를 생략할 수 있습니다.
  • 핸들러 에서, 핸들러 인수의 생성자와 소멸자 실행을 제외하고 프로그램의 의미가 변경되지 않는 경우, 핸들러 매개변수를 예외 객체의 별칭으로 처리함으로써 핸들러 인수의 복사 초기화를 생략할 수 있습니다.
(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를 직접 생성합니다; 이동 없음
}
반환 타입의 소멸자는 return 문 시점에서 접근 가능해야 하고 삭제되지 않아야 합니다. 비록 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 복사(/이동) 연산만 생략 가능했으나,
비복사(/이동) 생성자가 복사 초기화로 선택될 수 있음
관련 복사 초기화의
모든 객체 생성을 생략

참고 항목