copy_initialization — 복사 초기화

copy_initialization — 복사 초기화 (Copy-initialization)

**복사 초기화(copy-initialization)**는 다른 객체로부터 객체를 초기화하는 방식이에요. = 기호를 사용해 초기화하거나, 값 전달, 값 반환, 예외 throw/catch 같은 상황에서 일어나요.

T a = b; 형태로 객체를 만들 때 적용돼요. 복사 초기화는 explicit 생성자를 고려하지 않는다는 점이 직접 초기화와 달라요.

출처: cppreference

본문

복사 초기화는 다른 객체로부터 객체를 초기화해요.

문법 (Syntax)

T object = other;
T object = {other};
f(other)
return other;
throw object;
catch (T object)
T array[N] = {other-sequence};

설명 (Explanation)

복사 초기화는 다음 상황에서 수행돼요.

  1. 비참조 타입 T의 이름 있는 변수(자동, 정적, 스레드 로컬)가 등호 뒤에 표현식이 오는 초기화자로 선언될 때
  2. 스칼라 타입 T의 이름 있는 변수가 등호 뒤에 중괄호로 감싼 표현식으로 선언될 때 (C++11부터 이건 목록 초기화로 분류되고, 수축 변환은 허용되지 않아요)
  3. 함수에 인자를 값으로 전달할 때
  4. 값을 반환하는 함수에서 반환할 때
  5. 예외를 값으로 던지거나 잡을 때
  6. 애그리거트 초기화의 일부로, 초기화자를 제공받은 각 요소를 초기화할 때

복사 초기화의 효과

  • T가 클래스 타입이고 초기화자가 T와 (cv 한정을 무시하고) 같은 클래스인 prvalue 표현식이면, 그 prvalue에서 만들어진 임시가 아니라 초기화자 표현식 자체가 목적 객체를 초기화하는 데 사용돼요 (복사 생략 참조).
  • 그 외에 T가 클래스 타입이고 other의 cv 한정 없는 타입이 T거나 T에서 파생된 클래스라면, T의 비-explicit 생성자를 검사해 과부하 해석으로 최적 매치를 선택하고 그 생성자로 객체를 초기화해요.
  • 그 외에 T가 클래스 타입이고 other의 타입이 T거나 그 파생이 아니거나, T가 비클래스 타입인데 other의 타입이 클래스 타입이면, other의 타입에서 T로(또는 변환 함수가 있으면 T에서 파생된 타입으로) 변환할 수 있는 사용자 정의 변환이 검사되고 과부하 해석으로 최적이 선택돼요. 변환 결과(변환 생성자라면 cv 한정 없는 T의 prvalue 임시)로 객체를 직접 초기화해요. (복사 생략으로 결과를 목적 객체 메모리에 직접 생성할 수 있지만, 적절한 생성자가 사용되지 않더라도 접근 가능해야 해요.)
  • 그 외 (네 Tother의 타입도 클래스 타입이 아니면), 필요하다면 표준 변환으로 other의 값을 cv 한정 없는 T로 변환해요.

노트 (Notes)

복사 초기화는 직접 초기화보다 덜 허용적이에요. explicit 생성자는 변환 생성자가 아니므로 복사 초기화에서 고려되지 않아요. other가 rvalue 표현식이면 복사 초기화 중에 이동 생성자가 선택될 수 있는데, 이것도 여전히 복사 초기화로 간주돼요.

암시적 변환은 복사 초기화의 관점에서 정의돼요. 이름 있는 변수의 복사 초기화에서의 등호 =는 대입 연산자와 관련이 없어요. 대입 연산자 오버로드는 복사 초기화에 영향을 주지 않아요.

예제 (Example)

#include <memory>
#include <string>
#include <utility>

struct A
{
    operator int() { return 12;}
};

struct B
{
    B(int) {}
};

int main()
{
    std::string s = "test";        // OK: 생성자가 non-explicit
    std::string s2 = std::move(s); // 이 복사 초기화는 이동을 수행

//  std::unique_ptr<int> p = new int(1); // 오류: 생성자가 explicit
    std::unique_ptr<int> p(new int(1));  // OK: 직접 초기화

    int n = 3.14;    // 부동-정수 변환
    const int b = n; // const 는 상관없음
    int c = b;       // ...어느 쪽이든

    A a;
    B b0 = 12;
//  B b1 = a;       // 오류: 'A' 에서 비스칼라 타입 'B' 로 변환 요청
    B b2{a};        // 동일, A::operator int() 후 B::B(int) 호출
    B b3 = {a};     //
    auto b4 = B{a}; //

//  b0 = a;         // 오류, 대입 연산자 오버로드 필요

    [](...){}(c, b0, b3, b4); // 사용된 척
}

std::string s = "test"처럼 등호를 쓰는 복사 초기화는 비-explicit 생성자만 고려해요. std::unique_ptrexplicit 생성자라서 = new int(1)로는 초기화할 수 없고, new int(1) 괄호 직접 초기화만 가능해요.

더 알아보기 (Learn more)

cppreference