센로그

[EC++] 12. 객체의 모든 부분을 빠짐없이 복사하자 본문

Effective/Effective C++

[EC++] 12. 객체의 모든 부분을 빠짐없이 복사하자

seeyoun 2024. 11. 18. 22:21

커스텀 복사 생성자 및 복사 대입 연산자 구현 시 주의할 점

  • 모든 데이터 멤버들을 빠짐없이 복사해야 한다.
  • 빠진 요소가 있더라도 컴파일러가 친절하게 알려주지는 않는다.

 


예시 상황

1. 고객을 나타내는 Customer 클래스가 있다.

void logCall(const string& funcName);

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

Customer::Customer(const Customer& rhs) : name(rhs.name) {
    logCall("Customer Copy Constructor");
}

Customer& Customer::operator=(const Customer& rhs) {
    logCall("Customer Copy Assignment Operator");
    name = rhs.name;
    return *this;
}
  • 커스텀 복사 생성자와 복사 대입연산자를 구현하였다.
  • 문제 없이 잘 돌아간다.

 

2. 그러나 이때 데이터 멤버 하나를 더 추가한다면?

class Date { ... };
class Customer {
public:
    ...
private:
    string name;
    Date lastTransaction;
};
  • 멤버가 추가되더라도 기존 복사 생성자와 복사 대입 연산자는 별도의 오류 없이 그대로 동작한다.
  • 즉, 객체의 전체가 아닌 일부만 복사하는 바보 함수가 된다.

 

3. 가장 문제되는 상황은 상속에서의 상황이다.

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("Priority Customer Copy Consturctor");
}

PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer* rhs) {
    logCall("Priority Customer Copy Assignment Operator");
    priority = rhs.priority;
    return *this;
}
  • Customer을 상속받는 PriorityCustomer 클래스가 있다.
  • PriorityCustomer의 복사함수는 선언된 멤버들을 전부 복사하고 있는 것 같다.
  • 그러나... Customer의 멤버함수는.. 복사가 되고 있지 않다!

 


해결 방안

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

PriorityCustomer& PriorityCustomer::operator= (const PriorityCustomer& rhs) {
    logCall("PriorityCustomer Copy Assignment Operator");
    Customer::operator=(rhs);	// 기본 클래스 부분을 대입
    priority = rhs.priority;
    return *this;
}
  • 파생 클래스의 복사 함수에서 기본 클래스의 복사 함수를 호출하게 만들자.
    • 복사 생성자의 경우, 초기화 리스트에서 기본 클래스의 복사 생성자를 호출한다.
    • 복사 대입 연산자의 경우, 함수 내부에서 기본 클래스의 복사 대입 연산자를 호출한다.

 


따라서, 객체의 복사 함수를 구현할 때는 다음 두가지를 꼭 확인한다.

  • 해당 클래스의 데이터 멤버를 모두 복사한다.
  • 이 클래스가 상속한 기본 클래스의 복사 함수를 호출한다.

 


그런데.. 복사 생성자랑 복사 대입 연산자는 내용이 비슷할텐데, 한쪽에서 다른 쪽을 호출하는 방식으로 쓰면 코드 중복도 피하고 좋지 않나?

  • 안됨.
    • 복사 생성자: 새로운 객체를 생성하고 다른 객체의 내용을 복사하여 초기화
    • 복사 대입 연산자: 이미 생성된(초기화 완료된) 객체에 다른 객체의 내용을 복사
  • 복사 대입 연산자에서 복사 생성자를 호출하는 경우
    • 이미 객체가 생성되어 있는 상황에서 또다시 새로운 객체를 생성한다고? 말 안됨
  • 복사 생성자에서 복사 대입 연산자를 호출하는 경우
    • 대입 연산자는 이미 초기화가 끝난 객체에 값을 넣어주는 거임. 아직 초기화도 안 된 객체에 대해 대입이라고? 말 안됨
Comments