복사 대입 연산자
복사 대입 연산자 (Copy assignment operator)
한 객체의 내용을 다른 객체에 "복사해서 담아" 주고 싶을 때 쓰는 게 복사 대입 연산자예요. 같은 클래스 타입의 인자로 호출될 수 있고, 이름이 operator=인 비템플릿 비정적 멤버 함수가 바로 그것이에요. 인자의 내용을 복사하되 인자 자체는 바꾸지 않죠.
출처: cppreference
본문
복사 대입 연산자는 같은 클래스 타입의 인자로 호출될 수 있고 인자의 내용을 복사하되 인자를 변경하지 않는, 이름이 operator=인 비템플릿 비정적 멤버 함수예요.
구문 (Syntax)
형식적인 복사 대입 연산자 구문에 대해서는 함수 선언을 참고해요. 아래 구문 목록은 유효한 복사 대입 연산자 구문의 일부만 보여줘요.
| 구문 | 번호 | |
|---|---|---|
return-type operator=(parameter-list); |
(1) | |
return-type operator=(parameter-list) function-body |
(2) | |
return-type operator=(parameter-list-no-default) = default; |
(3) | (since C++11) |
return-type operator=(parameter-list) = delete; |
(4) | (since C++11) |
return-type class-name::operator=(parameter-list) function-body |
(5) | |
return-type class-name::operator=(parameter-list-no-default) = default; |
(6) | (since C++11) |
| 항목 | - | 설명 |
|---|---|---|
class-name |
- | 복사 대입 연산자를 선언하는 클래스로, 아래 설명에서 클래스 타입을 T로 나타냄 |
parameter-list |
- | T, T&, const T&, volatile T&, const volatile T& 타입 중 하나인 단 하나의 매개변수 목록 |
parameter-list-no-default |
- | 위 타입 중 하나이고 기본 인자를 갖지 않는 단 하나의 매개변수 목록 |
function-body |
- | 복사 대입 연산자의 함수 본문 |
return-type |
- | 임의 타입이지만, 대입을 연쇄할 수 있도록 T&가 선호됨 |
설명 (Explanation)
- 클래스 정의 안에서의 복사 대입 연산자 선언. 2-4) 클래스 정의 안에서의 복사 대입 연산자 정의.
- 복사 대입 연산자가 명시적으로 defaulted됨.
- 복사 대입 연산자가 deleted됨. 5,6) 클래스 정의 밖에서의 복사 대입 연산자 정의 (클래스는 (1)의 선언을 포함해야 함).
- 복사 대입 연산자가 명시적으로 defaulted됨.
struct X
{
X& operator=(X& other); // copy assignment operator
X operator=(X other); // pass-by-value is allowed
// X operator=(const X other); // Error: incorrect parameter type
};
union Y
{
// copy assignment operators can have syntaxes not listed above,
// as long as they follow the general function declaration syntax
// and do not violate the restrictions listed above
auto operator=(Y& other) -> Y&; // OK: trailing return type
Y& operator=(this Y& self, Y& other); // OK: explicit object parameter
// Y& operator=(Y&, int num = 1); // Error: has other non-object parameters
};
복사 대입 연산자는 오버로드 해석이 선택할 때마다, 예를 들어 객체가 대입 표현식의 왼쪽에 나타날 때 호출돼요.
암시적으로 선언된 복사 대입 연산자 (Implicitly-declared copy assignment operator)
클래스 타입에 사용자 정의 복사 대입 연산자가 없으면, 컴파일러는 항상 클래스의 inline public 멤버로 하나를 선언해요. 이 암시적으로 선언된 복사 대입 연산자는 다음이 모두 참이면 T& T::operator=(const T&) 형태를 가져요:
T의 각 직접 기반B가B,const B&,const volatile B&타입 매개변수를 갖는 복사 대입 연산자를 가짐T의 클래스 타입 또는 클래스 타입 배열인 각 비정적 데이터 멤버M이M,const M&,const volatile M&타입 매개변수를 갖는 복사 대입 연산자를 가짐
그 외에는 암시적으로 선언된 복사 대입 연산자가 T& T::operator=(T&)로 선언돼요.
이 규칙 때문에 암시적으로 선언된 복사 대입 연산자는 volatile lvalue 인자에 바인딩할 수 없어요.
클래스는 여러 복사 대입 연산자를 가질 수 있어요, 예를 들어 T& T::operator=(T&)와 T& T::operator=(T)를 모두 가질 수 있죠. 사용자 정의 복사 대입 연산자가 있어도 default 키워드로 암시적으로 선언된 복사 대입 연산자의 생성을 강제할 수 있어요.(since C++11)
암시적으로 선언된 (또는 첫 번째 선언에서 defaulted된) 복사 대입 연산자는 dynamic exception specification(until C++17)noexcept specification(since C++17)에 설명된 예외 명세를 가져요.
복사 대입 연산자는 모든 클래스에 항상 선언되기 때문에, 기반 클래스의 대입 연산자는 항상 가려져요(hidden). using-declaration으로 기반 클래스에서 대입 연산자를 가져오고 그 인자 타입이 파생 클래스의 암시적 대입 연산자의 인자 타입과 같을 수 있다면, 그 using-declaration도 암시적 선언에 가려져요.
암시적으로 정의된 복사 대입 연산자 (Implicitly-defined copy assignment operator)
암시적으로 선언된 복사 대입 연산자가 deleted도 아니고 trivial도 아니면, odr-used되거나 상수 평가(since C++14)에 필요할 때 컴파일러가 정의해요(즉 함수 본문이 생성되고 컴파일됨). 유니온 타입의 경우 암시적으로 정의된 복사 대입은 객체 표현(object representation)을 복사해요(std::memmove처럼). 비유니온 클래스 타입의 경우, 이 연산자는 객체의 직접 기반과 비정적 데이터 멤버를 초기화 순서대로 멤버별 복사 대입(member-wise copy assignment)을 수행해요. 스칼라에는 내장 대입을, 배열에는 멤버별 복사 대입을, 클래스 타입에는 (비가상으로 호출되는) 복사 대입 연산자를 사용해요.
| 참고 | (since) |
|---|---|
클래스 T의 암시적으로 정의된 복사 대입 연산자는, T가 literal 타입이고 각 직접 기반 클래스 부분 객체를 복사하도록 선택된 대입 연산자가 constexpr 함수이며, T의 클래스 타입(또는 그 배열)인 각 비정적 데이터 멤버를 복사하도록 선택된 대입 연산자가 constexpr 함수이면 constexpr이다. |
(since C++14)(until C++23) |
클래스 T의 암시적으로 정의된 복사 대입 연산자는 constexpr이다. |
(since C++23) |
| 참고 | (since) |
|---|---|
T에 사용자 선언 소멸자 또는 사용자 선언 복사 생성자가 있으면 암시적으로 정의된 복사 대입 연산자의 생성은 deprecated된다. |
(since C++11) |
삭제된 복사 대입 연산자 (Deleted copy assignment operator)
클래스 T에 대한 암시적으로 선언되거나 명시적으로 defaulted된(since C++11) 복사 대입 연산자는 다음 조건 중 하나라도 만족하면 undefined(until C++11)defined as deleted(since C++11)돼요:
T가 const 한정된 비클래스 타입(또는 그 다차원 배열)의 비정적 데이터 멤버를 갖는 경우T가 참조 타입의 비정적 데이터 멤버를 갖는 경우T가 클래스 타입M(또는 그 다차원 배열)의 잠재적으로 구성되는 부분 객체(subobject)를 갖는데,M의 복사 대입 연산자를 찾기 위한 오버로드 해석이- 사용 가능한 후보를 만들지 못하거나,
- 부분 객체가 variant 멤버인 경우 비trivial 함수를 선택하는 경우
| 참고 | (since) |
|---|---|
T가 이동 생성자 또는 이동 대입 연산자를 선언하면 클래스 T의 암시적으로 선언된 복사 대입 연산자는 deleted로 정의된다. |
(since C++11) |
trivial 복사 대입 연산자 (Trivial copy assignment operator)
클래스 T의 복사 대입 연산자는 다음이 모두 참이면 trivial이에요:
- 사용자 제공(user-provided)이 아님 (즉 암시적으로 정의되거나 defaulted됨)
T에 가상 멤버 함수가 없음T에 가상 기반 클래스가 없음T의 각 직접 기반에 대해 선택된 복사 대입 연산자가 trivial임T의 각 비정적 클래스 타입(또는 클래스 타입 배열) 멤버에 대해 선택된 복사 대입 연산자가 trivial임
trivial 복사 대입 연산자는 std::memmove처럼 객체 표현의 복사본을 만들어요. C 언어와 호환되는 모든 데이터 타입(POD 타입)은 trivially copy-assignable이에요.
적격 복사 대입 연산자 (Eligible copy assignment operator)
| 정의 | (since) |
|---|---|
| 복사 대입 연산자는 사용자 선언이거나 암시적으로 선언되고 정의 가능한 경우에 적격(eligible)이다. | (until C++11) |
| 복사 대입 연산자는 deleted가 아니면 적격이다. | (since C++11)(until C++20) |
| 복사 대입 연산자는 다음 조건이 모두 충족되면 적격이다: deleted가 아님. 연관 제약(있다면)이 만족됨. 연관 제약이 만족되는 복사 대입 연산자 중 더 제한적(constrained)인 것이 없음. | (since C++20) |
적격 복사 대입 연산자의 trivial 여부는 클래스가 trivially copyable 타입인지 결정해요.
참고 (Notes)
복사 대입과 이동 대입 연산자를 모두 제공하면, 오버로드 해석은 인자가 rvalue(이름 없는 임시 같은 prvalue 또는 std::move의 결과 같은 xvalue)일 때 이동 대입을, 인자가 lvalue(이름 있는 객체 또는 lvalue 참조를 반환하는 함수/연산자)일 때 복사 대입을 선택해요. 복사 대입만 제공하면 모든 인자 범주가 그것을 선택해요(값 또는 const 참조로 인자를 받는 한, rvalue는 const 참조에 바인딩될 수 있으므로), 그래서 이동이 불가능할 때 복사 대입이 이동 대입의 폴백(fallback)이 돼요.
상속 격자(inheritance lattice)에서 둘 이상의 경로로 접근 가능한 가상 기반 클래스 부분 객체가 암시적으로 정의된 복사 대입 연산자에 의해 둘 이상 할당되는지 여부는 불특정이에요(이동 대입에도 동일하게 적용).
사용자 정의 복사 대입 연산자의 기대 동작에 대한 추가 세부사항은 대입 연산자 오버로딩을 참고해요.
예제 (Example)
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
struct A
{
int n;
std::string s1;
A() = default;
A(A const&) = default;
// user-defined copy assignment (copy-and-swap idiom)
A& operator=(A other)
{
std::cout << "copy assignment of A\n";
std::swap(n, other.n);
std::swap(s1, other.s1);
return *this;
}
};
struct B : A
{
std::string s2;
// implicitly-defined copy assignment
};
struct C
{
std::unique_ptr<int[]> data;
std::size_t size;
// user-defined copy assignment (non copy-and-swap idiom)
// note: copy-and-swap would always reallocate resources
C& operator=(const C& other)
{
if (this != &other) // not a self-assignment
{
if (size != other.size) // resource cannot be reused
{
data.reset(new int[other.size]);
size = other.size;
}
std::copy(&other.data[0], &other.data[0] + size, &data[0]);
}
return *this;
}
};
int main()
{
A a1, a2;
std::cout << "a1 = a2 calls ";
a1 = a2; // user-defined copy assignment
B b1, b2;
b2.s1 = "foo";
b2.s2 = "bar";
std::cout << "b1 = b2 calls ";
b1 = b2; // implicitly-defined copy assignment
std::cout << "b1.s1 = " << b1.s1 << "; b1.s2 = " << b1.s2 << '\n';
}
출력 (Output):
a1 = a2 calls copy assignment of A
b1 = b2 calls copy assignment of A
b1.s1 = foo; b1.s2 = bar
A는 사용자 정의 복사 대입(copy-and-swap idiom)을 쓰고, 이를 상속하는 B는 암시적으로 정의된 복사 대입을 써요. 그래서 b1 = b2에서도 기반 클래스인 A의 복사 대입이 먼저 호출되고 그다음 B의 멤버가 복사되죠.
더 알아보기 (Learn more)
- 변환 생성자 — 암시적 변환을 일으키는 생성자.
- 복사 생성자 — 같은 타입 객체로부터 초기화하는 생성자.
- 복사 생략 — 복사·이동 생성이 생략될 수 있는 상황.
- 기본 생성자, 소멸자,
explicit. - 복사 초기화·직접 초기화·목록 초기화 등 각 초기화 방식.
- 이동 대입, 이동 생성자.