[Effective C++] 항목12 : 객체의 모든 부분을 빠짐없이 복사하자

Jangmanbo·2023년 3월 22일
0

Effective C++

목록 보기
12/26

객체 복사 함수에는 복사 생성자복사 대입 연산자가 있다.
항목 5에서 보았듯이 이러한 객체 복사 함수는 필요할 때 컴파일러가 자동으로 생성하지만, 사용자가 직접 구현할 수도 있다.

직접 구현한 객체 복사 함수

void logCall(const std::string& funcName);	// 로그 출력 함수

class Customer {
public:
	...
    Customer(const Customer& rhs);
    Customer& operator=(const Customer& rhs);
   	...
private:
	std::string name;
};

Customer::Customer(const Customer& rhs):name(rhs.name)
{
	logCall("Customer copy constructor");
}

Customer& Customer::operator=(const Customer& rhs)
{
	logCall("Customer copy assignment constructor");
    
    name=rhs.name;	// rhs의 데이터 복사
    
    return *this;	// 항목 10
}

고객을 나타내는 클래스를 생성했다.
복사 함수를 호출할 때마다 로그를 출력하도록 하였다.

새로운 데이터 멤버를 추가했을 때

class Date { ... };

class Customer {
public:
	...
private:
	std::string name;
    Date lastTransation;
};

데이터 멤버를 하나 추가했는데 복사 함수는 그대로라면 복사 함수의 동작은 완전 복사가 아니라 부분 복사가 된다. (name만 복사, lastTransaction은 복사X)
그럼에도 컴파일러는 어떤 경고도 보내지 않는다.

결국 사용자는 클래스에 데이터 멤버를 추가하면 복사 생성자, 복사 대입연산자, 모든 생성자를 갱신해야 한다.

이러한 문제는 클래스 상속에서 더욱 두드러진다.


직접 구현한 파생 클래스의 객체 복사 함수

// Customer 클래스를 상속
class PriorityCustomer: public Customer {
public:
	...
    PriorityCustomer(const PriorityCustomer& rhs);
    PriorityCustomer& operator=(const PriorityCustomer& rhs);
    ...
private:
	int priority;
};

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):priority(rhs.priority)
{
	logCall("PrioirtyCustomer copy constructor");
}

PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
	logCall("PrioirtyCustomer copy assignment constructor");
    
    priority=rhs.priority;
    
    return *this;
}

언뜻 보았을 땐 문제가 없어보인다. 객체 복사 함수에서 PriorityCustomer의 멤버 변수 priority를 복사하고 있기 때문이다.

그러나 Customer로부터 상속한 데이터 멤버들은 복사가 안되고 있다!

PriorityCustomer 복사 생성자

  • Customer 클래스에 넘길 인자들조차 명시X
  • Customer의 데이터 멤버들은 인자 없이 실행되는 Customer 생성자(=기본 생성자)에 의해 초기화

PriorityCustomer 복사 대입 연산자

  • Customer 데이터 멤버들에 대한 대입X
  • 기존에 가지고 있던 값을 그대로 유지


해결법

// 기본 클래스의 복사 생성자 호출
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):Customer(rhs), priority(rhs.priority)
{
	logCall("PrioirtyCustomer copy constructor");
}

PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
	logCall("PrioirtyCustomer copy assignment constructor");
    
    Customer::operator=(rhs);	// 기본 클래스의 복사 대입 연산자 호출
    priority=rhs.priority;
    
    return *this;
}

파생 클래스의 복사 함수에서 기본 클래스의 복사 함수를 호출하여 해결한다.


객체 복사 함수 작성법
1. 해당 클래스의 데이터 멤버를 모두 복사
2. 이 클래스가 상속한 기본 클래스의 복사 함수 호출


부록

두 복사 함수(복사 생성자, 복사 대입 연산자)의 본문이 비슷하게 나오는 경우들도 종종 발생한다.
한 번쯤은 한쪽에서 다른 쪽을 호출하게 만들면 어떨까 라는 생각을 할 수도 있다.

1. 복사 대입 연산자가 복사 생성자를 호출

  • 이미 존재하는 객체를 생성하게 된다.(물론 불가능)

2. 복사 생성자가 복사 대입 연산자를 호출

  • 생성자는 새로 만들어진 객체를 초기화, 대입 연산자는 이미 초기화가 끝난 객체에 값을 대입
  • 초기화도 되지 않은 객체에 대해 대입을 하게 된다.(마찬가지로 불가능)

해결법
양쪽에서 겹치는 부분을 별도의 멤버 함수로 분리하여 이 함수를 호출하자!

0개의 댓글