copy_assignment — 복사 대입 연산자
copy_assignment — 복사 대입 연산자
**복사 대입 연산자(copy assignment operator)**는 이름이 operator=인 비템플릿 비정적 멤버 함수로, 같은 클래스 타입의 인자로 호출될 수 있고 인자를 변경하지 않고 그 내용을 복사해요.
a = b;처럼 객체에 다른 객체를 대입할 때 호출돼요. 자원을 관리하는 클래스에서는 깊은 복사·자원 재사용 등을 이 연산자에서 구현해요.
출처: cppreference
본문
복사 대입 연산자는 이름이 operator=인 비템플릿 비정적 멤버 함수이며, 같은 클래스 타입의 인자로 호출될 수 있고 인자를 변경하지 않고 그 내용을 복사해요.
문법 (Syntax)
return-type operator=( parameter-list ) ;
return-type operator=( parameter-list ) function-body
return-type operator=( parameter-list-no-default ) = default;
return-type operator=( parameter-list ) = delete;
return-type class-name :: operator=( parameter-list ) function-body
return-type class-name :: operator=( parameter-list-no-default ) = default;
class-name— 복사 대입 연산자를 선언하는 클래스 (아래에서 클래스 타입을T라 함)parameter-list— 하나의 매개변수만 가진 매개변수 목록. 타입은T,T&,const T&,volatile T&,const volatile T&function-body— 복사 대입 연산자의 함수 본문return-type— 어떤 타입이든 가능하지만, 대입 체이닝을 위해T&가 선호돼요.
설명 (Explanation)
복사 대입 연산자는 과부하 해석에 의해, 예를 들어 객체가 대입 표현식의 왼쪽에 나타날 때 선택돼요.
암시적으로 선언된 복사 대입 연산자
클래스 타입에 사용자 정의 복사 대입 연산자가 없으면, 컴파일러는 항상 하나를 클래스의 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) 둘 다). C++11부터 default로 암시적 복사 대입을 강제할 수 있어요.
복사 대입 연산자는 모든 클래스에 항상 선언되므로, 기반 클래스의 대입 연산자는 항상 가려져요. using 선언으로 기반 클래스 대입 연산자를 가져오고 그 인자 타입이 파생 클래스의 암시적 대입 연산자와 같을 수 있다면, 그 using 선언도 암시적 선언에 가려져요.
암시적으로 정의된 복사 대입 연산자
암시적으로 선언된 복사 대입 연산자가 삭제되지도 않았고 trivial도 아니라면, ODR-사용되거나 상수 평가에 필요할 때 컴파일러가 정의해요. 유니언 타입에 대해서는 객체 표현을 복사하고(std::memmove처럼), 비유니온 클래스 타입에 대해서는 객체의 직접 기반과 비정적 데이터 멤버를 초기화 순서대로 멤버별 복사 대입해요 — 스칼라는 기본 대입, 배열은 멤버별 복사 대입, 클래스 타입은 (비가상으로 호출되는) 복사 대입 연산자로 처리해요.
C++14/C++23부터 암시적으로 정의된 복사 대입 연산자는 constexpr일 수 있어요. C++11부터 T에 사용자 선언 소멸자나 사용자 선언 복사 생성자가 있으면 암시적으로 정의된 복사 대입 연산자의 생성은 deprecated예요.
삭제된 복사 대입 연산자
클래스 T의 암시적으로 선언되거나 명시적으로 default 처리된 복사 대입 연산자는 다음 조건 중 하나라도 만족하면 삭제된 것으로 정의돼요.
T가 const 한정 비클래스 타입의 비정적 데이터 멤버를 가짐T가 참조 타입의 비정적 데이터 멤버를 가짐T가 복사 대입 연산자 오버로드 해석이 사용 가능한 후보를 만들지 못하거나 변형 멤버에서 비trivial 함수를 선택하는 클래스 타입의 부분 객체를 가짐
C++11부터 T가 이동 생성자나 이동 대입 연산자를 선언하면 암시적으로 선언된 복사 대입 연산자는 삭제된 것으로 정의돼요.
Trivial 복사 대입 연산자
다음이 모두 참이면 trivial이에요.
- 사용자 제공이 아님 (암시적으로 정의되거나 default 처리됨)
T가 가상 멤버 함수가 없음T가 가상 기반 클래스가 없음T의 각 직접 기반에 대해 선택된 복사 대입 연산자가 trivialT의 클래스 타입 멤버 각각에 대해 선택된 복사 대입 연산자가 trivial
Trivial 복사 대입 연산자는 std::memmove처럼 객체 표현을 복사해요. C 언어와 호환되는 모든 데이터 타입(POD 타입)은 trivially copy-assignable이에요.
노트 (Notes)
복사와 이동 대입 연산자가 모두 있으면, 인자가 rvalue일 땐 이동 대입, lvalue일 땐 복사 대입이 선택돼요. 복사 대입만 있으면 모든 인자 카테고리가 복사 대입을 선택해요 (이동을 못 쓸 때 복사 대입이 폴백이 됨).
예제 (Example)
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
struct A
{
int n;
std::string s1;
A() = default;
A(A const&) = default;
// 사용자 정의 복사 대입 (copy-and-swap 관용구)
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;
// 암시적으로 정의된 복사 대입
};
struct C
{
std::unique_ptr<int[]> data;
std::size_t size;
// 사용자 정의 복사 대입 (copy-and-swap 아님)
// 참고: copy-and-swap 은 항상 자원을 재할당
C& operator=(const C& other)
{
if (this != &other) // 자기 대입 아님
{
if (size != other.size) // 자원 재사용 불가
{
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; // 사용자 정의 복사 대입
B b1, b2;
b2.s1 = "foo";
b2.s2 = "bar";
std::cout << "b1 = b2 calls ";
b1 = b2; // 암시적으로 정의된 복사 대입
std::cout << "b1.s1 = " << b1.s1 << "; b1.s2 = " << b1.s2 << '\n';
}
출력:
a1 = a2 calls copy assignment of A
b1 = b2 calls copy assignment of A
b1.s1 = foo; b1.s2 = bar
A는 copy-and-swap 방식으로 사용자 정의 복사 대입을 구현하고, C는 자기 대입 검사와 자원 재사용을 포함한 복사 대입을 구현했어요. B는 멤버별 복사를 하는 암시적 복사 대입을 사용해요.