Item 12: 객체의 모든 부분을 빠짐없이 복사하자
1. 객체 지향 시스템 중 설계가 잘된 것들을 보면, 객체를 복사하는 함수가 몇개인가?
- 복사생성자(Copy Constructor)
- 복사 대입 연산자(Copy Assignment Operator)
이렇게 2개이다. 이 두 함수를 복사함수(copying function)이라고 부른다.
2. 객체 복사 함수를 사용자가 직접 정의하지 않으면 컴파일러가 자동으로 만들어 주는데, 이때 컴파일러가 만든 복사함수의 기능은 뭔가?
기본적으로 객체가 갖고 있는 데이터를 빠짐없이 복사하는 것이다.
3. 복사함수를 사용자가 만들 때 문제는 무엇인가?
컴파일러는 사용자가 만든 복사함수가 명백히 틀려도 이를 알려주지 않는다.
void logCall(const std::string& funcName); // 로그 기록내용을 만든다.
class Date { ... }; // 날짜 정보를 위한 클래스
class Customer {
public:
...
Customer(const Customer& rhs);
Customer& operator=(const Customer& rhs);
...
private:
std::string name;
Date lastTransaction;
};
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; // 항목 10
}
name만 복사하고 lastTransactoin은 복사하지 않고 있지만 정상적으로 컴파일이 진행된다.
4. 복사함수에서 클래스 상속과 관련해서 주의할 점은 무섯인가?
기본 클래스의 데이터까지 다 복사하도록 주의해야 한다.
- 해당 클래스의 데이터 멤버를 모두 복사하고
- 이 클래스가 상속한 기본 클래스의 복사 함수도 호출해 주자
class PriorityCustomer: public Customer {
public:
...
PriorityCustomer(const PriorityCustomer& rhs);
PriorityCustomer& operator=(const PriorityCustomer& rhs);
...
private:
int priority;
};
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;
}
5. 복사생성자와 복사 대입연산자 사이의 코드 중복을 해결하는 방법과 주의점은?
해결법
- 양대 복사함수들 간에 코드가 많이 중복된다면 해당 코드만을 담는 private 멤버함수를 만들어서 사용한다.(보통 init 류 함수)
주의점
- 복사 생성자에서 복사 대입 연산자를 호출하거나 반대로 하지 않는다.
- 생성중에 복사대입연산자 호출안된다.(옛날에 가능했으나 위험성이 있는 코드다)
- 복사대입연산자에서 복사생성자를 호출하는 것도 말이 안된다. 생성자는 생성되는 객체를 위한 것이고 대입연산자는 이미 만들어진 객체에 대해서 다루는 함수다.
정리
- 객체 복사함수는 주어진 객체의 모든 데이터 멤버 및 모든 기본 클래스 부분을 빠뜨리지 말고 복사해야 한다.
- 클래스의 복사 함수 두 개를 구현할 때, 한쪽을 이용해서 다른 쪽을 구현하려는 시도는 절대로 하지 말자. 대신, 공통된 동작을 제 3의 함수에 분리해 놓고 양쪽에서 이것을 호출하도록 하자.