728x90
반응형

객체의 모든 부분을 빠짐없이 복사하자

객체의 안쪽 부분을 캡슐화된 객체 지향 시스템 중 설계가 잘 된 것들을 보면 복사 생성자와 복사 대입 연산자 두 가지가 있고, 이 둘을 통틀어 객체 복사 함수라고 부른다.

이렇게 컴파일러가 생성한 복사 함수는 객체의 모든 부분을 빠짐없이 복사해주지만, 개발자가 직접 복사 함수를 선언한다면 구현한 복사 함수가 틀린 경우에도 알려주지 않는다.(ex. 모든 객체의 복사가 이루어지지 않는 경우..)

 

예제

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 operator");
	name = rhs.name;
    return *this;
}

이렇게 보면 문제 될 것이 없지만 데이터 멤버 하나만 추가해도 완전 복사가 아닌 부분 복사가 되어버리지만, 컴파일러는 이러한 점을 고지해주지 않는다.

즉, 데이터 멤버를 추가했으면 추가한 데이터 멤버를 처리할 수 있도록 복사 함수를 다시 작성해야 한다.(생성자도 갱신해야 하고, operator= 함수도 갱신해야 함)

 

상속 클래스의 복사

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(“PriorityCustomer copy constructor”);
}
 
PriorityCustomer& PriorityCustomer::operator = (const PriorityCustomer& rhs)
{
    logCall(“PriorityCustomer copy assignment operator”);
    priority = rhs.priority;
    return *this;
}

PriorityCustomer클래스의 복사 함수는 PriorityCustomer의 모든 것을 복사하고 있는 것처럼 보이지만, Customer로부터 상속한 데이터 멤버들의 사본은 복사가 되지 않고 있음을 알 수 있다.

또한, PriorityCustomer의 복사 생성자에는 기본 클래스 생성자에 넘길 인자들도 명시되어 있지 않아서 Customer생성자는 기본 생성자에 의해 초기화된다.

그리고 PriorityCustomer의 복사 대입 연산자의 경우에는 기본 클래스의 데이터 멤버를 건드릴 시도도 하지 않기 때문에, 기본 클래스의 데이터 멤버는 변경되지 않고 그대로 있게 된다.

 

따라서 복사 함수를 스스로 만든다면 아래와 같은 방법으로 기본 클래스 부분을 빠트리지 않고 복사하도록 해야 함을 잊지 말자.

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;
}

 

요약

  • 객체 복사 함수는 주어진 객체의 모든 데이터 멤버 및 기본 클래스 부분을 빠트리지 않고 복사해야 한다.
  • 클래스의 복사 함수 두 개를 구현할 때, 한쪽을 이용해서 다른 쪽을 구현하려는 시도는 절대로 하지 말아야 한다. 그 대신, 공통된 동작을 제3의 함수에다 분리해놓고 양쪽에서 이것을 호출하게 만들어서 해결할 수 있다.
728x90
반응형