객체지향 시스템에서 잘 설계된 것들을 보면 객체를 복사하는 함수가 딱 둘만 있는 것을 알 수 있다.
복사 생성자와 복사대입 연산자가 그 두가지 함수인데, 이 둘을 통틀어서 객체 복사 함수 (copying function) 이라고 불러진다.
객체 복사함수 선언의 의미는 컴파일러 동작을 100% 신뢰할 수 없다는 것을 의미하는데, 아래 코드를 보자.
void logCall( const std::strign& funcName); // 로그 기록내용 생성
class Apple {
public :
....
Apple ( const Apple& a );
Apple& operator=(const Apple& a );
...
private:
string brix;
};
Apple::Apple( const Apple& a ) : brix( a.brix ) // a 의 데이터 복사
{
logCall ( *Apple copy constructor* );
}
Apple& Apple::operator=(const Apple& a )
{
logCall(*Apple copy assignment operator*);
name = a.brix; // a의 데이터 복사
return *this; // 대입연산자 operator는 *this 를 반환, C++ 세계에서의 de-Facto
}
위 코드는 그닥 문제가 없는 코드이다. 그런데 멤버변수가 추가되는 경우, 생각처럼 동작하지 않게된다.
class Apple {
public :
....
Apple ( const Apple& a );
Apple& operator=(const Apple& a );
...
private:
string brix;
string producer;
};
사과의 생산자 정보가 추가되었는데, 이런경우 복사함수의 동작이 완전복사가 아닌 부분복사(Partial copy)가 된다.
brix 값은 operator= 에 구현되어 있어 복사되지만, producer는 복사되지 않기 때문이다.
여기서 문제는 이렇게 복사 값이 누락 될 가능성이 있음에도, 컴파일러가 알려주지 않는다는게 문제라는 것이다.
이 문제는 클래스 상속이 일어나는 경우에도 발생하는데, 아래 코드를 보자
class GreenApple : public Apple {
public:
...
GreenApple ( const GreenApple& a );
GreenApple& operator= ( const GreenApple& a );
...
private:
int color;
};
GreenApple::GreenApple( const GreenApple& a ) : color( a.color )
{
logCall( "Copy constructor for color." );
}
GreenApple& GreenApple::operator= (const GreenApple& a)
{
logCall("GreenApple copy assignment operator");
color = a.color;
return *this;
}
GreenApple 클래스의 복사함수는 GreenApple의 모든 내용을 복사하는 것으로 보인다. 그런데 Apple클래스의 데이터들도 상속을 받았 기 때문에 GreenApple 이 갖게 되는데, 이 부분은 복사가 되지 않고 있다.
GreenApple 복사 생성자에는 기본 클래스 생성자에 넘길 인자들도 명시가 되지 않아 Apple의 기본 생성자에 의해서만 초기화 된다. 이에따라 brix와 producer에 대한 값만 초기화를 해주게 될 것이다.
결론적으로 상속을 하게되는 경우 하위 클래스에서 상위 클래스의 데이터 복사에 대해 주의가 필요한데, 이는 하위 클래스의 복사함수에서 상위 클래스의 복사함수를 만들어 처리가 가능하다.
GreenApple::GreenApple( const GreenApple& a) : Apple(a), color( a.color)
{
logCall( "GreenApple copy constructor" );
}
GreenApple& GreenApple::operator= ( const GreenApple& a )
{
logCall( " GreenApple copy assignment operator");
Apple::operator=(a); // 기본 클래스 부분을 대입한다.
color = a.color;
return *this;
}
복사 함수를 만들 때, 모든 부분을 복사가 필요하다는 말이 이런 상속이나 새로 멤버변수가 추가되는 경우 이에 상응하는 추가 구현이 필요하다는 것을 의미하며 이를 위해서 아래 두 가지를 확인하면 될 것이다.
1. 해당 클래스의 데이터 멤버를 모두 복사
2. 상속 시 상위클래스 복사함수의 호출
복사 생성자와 복사 대입 연산자에 나타나는 코드 중복을 제거하기 위해서는 중복 코드를 별도의 멤버함수에 분리 후 해당 함수를 호출하게 하는 것이다. 이런 함수는 대체로 private 멤버로 두고, init_*** 이름을 갖게 된다.
'Working on' 카테고리의 다른 글
NVIDIA NVTAGS ( Topology-Aware GPU Selection ) 메뉴얼 읽기 - 2 (0) | 2021.06.07 |
---|---|
[Effective C++] 자원 관리에는 객체를 활용한다 (0) | 2021.05.23 |
[Effective C++] operator= 에서는 자기대입에 대한 처리가 빠지지 않게하자 / 복사 후 맞 바꾸기 (Copy and Swap) (0) | 2021.05.19 |
[Effective C++] operator= 에서는 자기대입에 대한 처리가 빠지지 않게하자 (0) | 2021.05.19 |
[Effective c++] 항목 10: 대입 연산자는 *this 참조 반환하게 하자. (0) | 2021.05.12 |