복사 초기화
복사 초기화 (Copy initialization)
객체를 다른 객체로부터 초기화하는 가장 자연스러운 방식 중 하나가 복사 초기화예요. 초기화식이 같은 타입이거나 그 타입으로 변환될 수 있을 때, =를 써서 객체를 만들어요.
출처: cppreference
본문
다른 객체로부터 객체를 초기화해요.
구문 (Syntax)
| 구문 | 번호 | |
|---|---|---|
T object = other; |
(1) | |
T object = {other}; |
(2) | (until C++11) |
f(other) |
(3) | |
return other; |
(4) | |
throw object; catch (T object) |
(5) | |
T array[N] = {other-sequence}; |
(6) |
설명 (Explanation)
복사 초기화는 다음 상황에서 수행돼요:
- 비참조 타입
T의 이름 있는 변수(자동, 정적, 또는 스레드 로컬)가 등호 다음에 표현식이 오는 초기화식으로 선언될 때. - (until C++11) 스칼라 타입
T의 이름 있는 변수가 등호 다음에 중괄호로 둘러싸인 표현식이 오는 초기화식으로 선언될 때 (참고: C++11부터는 이것이 목록 초기화로 분류되고, 축소 변환은 허용되지 않아요). - 인자를 함수에 값으로 전달할 때.
- 값을 반환하는 함수에서 반환할 때.
- 예외를 값으로 던지거나 잡을 때.
- 집합체 초기화의 일부로, 초기화식이 제공된 각 요소를 초기화할 때.
복사 초기화의 효과는 다음과 같아요:
| 참고 | (since) |
|---|---|
먼저, T가 클래스 타입이고 초기화식이 cv-unqualified 타입이 T와 같은 클래스인 prvalue 표현식이면, 그 초기화식 자체를(실체화된 임시가 아니라) 목적지 객체 초기화에 사용한다: 복사 생략을 참고. |
(since C++17) |
- 그 외에는, (since C++17)
T가 클래스 타입이고other타입의 cv-unqualified 버전이T이거나T에서 파생된 클래스이면,T의 비-explicit 생성자를 조사하고 오버로드 해석으로 최적 일치를 선택해요. 그 생성자를 호출해 객체를 초기화해요. - 그 외에는,
T가 클래스 타입이고other타입의 cv-unqualified 버전이T또는T에서 파생된 것이 아니거나,T가 비클래스 타입이지만other타입이 클래스 타입이면,other타입에서T로 변환할 수 있는(또는T가 클래스 타입이고 변환 함수를 사용할 수 있으면T에서 파생된 타입으로) 사용자 정의 변환을 조사하고 오버로드 해석으로 최적을 선택해요. 변환 생성자가 사용되었으면 변환 결과(변환 생성자를 썼다면T의 cv-unqualified 버전의 rvalue 임시(until C++11)prvalue 임시(since C++11)(until C++17)prvalue 표현식(since C++17))로 객체를 직접 초기화해요. 마지막 단계는 보통 최적화로 제거되고 변환 결과가 대상 객체에 할당된 메모리에 직접 생성되지만, 적절한 생성자(이동 또는 복사)는 사용되지 않더라도 접근 가능해야 해요.(until C++17) - 그 외에는 (
T도other타입도 클래스 타입이 아니면), 필요하다면 표준 변환을 사용해other의 값을T의 cv-unqualified 버전으로 변환해요.
참고 (Notes)
복사 초기화는 직접 초기화보다 덜 관대해요: explicit 생성자는 변환 생성자가 아니므로 복사 초기화에서 고려되지 않아요.
struct Exp { explicit Exp(const char*) {} }; // not convertible from const char*
Exp e1("abc"); // OK
Exp e2 = "abc"; // Error, copy-initialization does not consider explicit constructor
struct Imp { Imp(const char*) {} }; // convertible from const char*
Imp i1("abc"); // OK
Imp i2 = "abc"; // OK
또한 복사 초기화에서의 암시적 변환은 초기화식에서 T를 직접 만들어야 해요. 반면 직접 초기화는 초기화식에서 T 생성자의 인자로의 암시적 변환을 기대해요.
struct S { S(std::string) {} }; // implicitly convertible from std::string
S s("abc"); // OK: conversion from const char[4] to std::string
S s = "abc"; // Error: no conversion from const char[4] to S
S s = "abc"s; // OK: conversion from std::string to S
other가 rvalue 표현식이면, 오버로드 해석이 이동 생성자를 선택해 복사 초기화 중에 호출해요. 이것도 여전히 복사 초기화로 간주돼요. 이 경우를 위한 특별한 용어(예: 이동 초기화)는 없어요.
암시적 변환은 복사 초기화 관점에서 정의돼요: T 타입의 객체가 표현식 E로 복사 초기화될 수 있으면, E는 T로 암시적으로 변환 가능해요.
이름 있는 변수의 복사 초기화에서의 등호 =는 대입 연산자와 관련이 없어요. 대입 연산자 오버로드는 복사 초기화에 아무 영향을 주지 않아요.
예제 (Example)
#include <memory>
#include <string>
#include <utility>
struct A
{
operator int() { return 12;}
};
struct B
{
B(int) {}
};
int main()
{
std::string s = "test"; // OK: constructor is non-explicit
std::string s2 = std::move(s); // this copy-initialization performs a move
// std::unique_ptr<int> p = new int(1); // error: constructor is explicit
std::unique_ptr<int> p(new int(1)); // OK: direct-initialization
int n = 3.14; // floating-integral conversion
const int b = n; // const doesn't matter
int c = b; // ...either way
A a;
B b0 = 12;
// B b1 = a; // < error: conversion from 'A' to non-scalar type 'B' requested
B b2{a}; // < identical, calling A::operator int(), then B::B(int)
B b3 = {a}; // <
auto b4 = B{a}; // <
// b0 = a; // < error, assignment operator overload needed
[](...){}(c, b0, b3, b4); // pretend these variables are used
}
std::unique_ptr는 explicit 생성자라서 = new int(1) 같은 복사 초기화가 안 되지만, 직접 초기화 p(new int(1))은 돼요. A a의 operator int() 변환을 거쳐 B b2{a};처럼 목록 초기화에서는 가능하죠.
더 알아보기 (Learn more)
- 복사 생략 — 복사·이동 생성이 생략될 수 있는 상황.
- 변환 생성자 — 암시적 변환을 일으키는 생성자.
- 복사 대입, 복사 생성자, 기본 생성자, 소멸자,
explicit. - 각 초기화 방식 (직접/목록/값/기본/제로 초기화 등).
- 이동 대입, 이동 생성자.