티스토리 뷰

728x90

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

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

컴파일러가 생성한 복사 함수를 쓰지 않고 개발자가 직접 객체 복사 함수를 선언한다면 구현한
복사 함수가 확실히 틀린 경우에도 알려주지 않는다.

 

예제 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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) //rhs의 데이터를 복사한다.
{
    logCall(“Customer copy constructor”);
}
 
Customer& Customer::operator = (const Customer& rhs)
{
    logCall(“Customer copy assignment operator”);
    name = rhs.name;               //rhs의 데이터를 복사한다.
    return *this;
}
cs


문제될 것이 하나도 없지만 데이터 멤버 하나를 추가시키면서 금이 가기 시작한다.

private:
 std::string name;
 Date lastTransaction;

이렇게 되면 부분 복사가 된다. lastTransaction은 복사하지 않고 컴파일러 경고 수준을 최대로 높여도 알려주는 컴파일러가 없다.

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

가장 사악하게 프로그래머를 괴롭히는 경우가 있는데 바로 클래스 상속입니다.

 

예제 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
cs


PriorityCustomer에 선언된 데이터 멤버를 모두 복사 하고 있지만, customer로부터 상속한 데이터 멤버들의 사본도 엄연히 PriorityCustomer 클래스에 들어 있는데, 이들은 복사도 안되고 있다.

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

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

복사 함수를 스스로 만든다면 기본 클래스 부분을 복사에서 빠뜨리지 않도록 주의하는데 기본클래스 부분은 private 멤버일 가능성이 아주 높아서 파생 클래스의 복사 함수 안에서 기본 클래스에 되응되는 복사함수를 호출하도록 만들면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
 
}
cs

객체의 복사 함수를 작성할 때는 해당 클래스이 데이터 멤버를 모두 복사하고 이 클래스가 상속한 기본 클래스의 복사 함수도 꼬박꼬박 호출해 주도록 한다.

총평

객체 복사 함수는 주어진 객체의 모든 데이터 멤버 및 모든 기본 클래스 부분을 빠뜨리지 말고 복사 한다.

클래스의 복사 함수 두 개를 구현할 때에는 공통된 동작을 제3의 함수에다 분리해 놓고 양쪽에서

이것을 호줄하게 만들어서 해결한다.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함
250x250