david's daily developer note

[C,C++] copy constructor 본문

[Develop] Native/C++ , C

[C,C++] copy constructor

mouse-david 2018. 6. 26. 21:15
728x90

copy constructor

이전의 글에서 이동생성자 (move constructor)를 설명하면서 잠시 언급했었다. C++11부터 복사 할당 및 이동 할당이 지원되는데, 이동생성자의 개념 설명과 이동생성자가 있을때, 복사생성자가 불리지 않는다는 등의 설명이었다. 

이 글에서는 복사생성자에 대한 이야기를 하려한다.
복사생성자의 원형은 다음과 같다.

class test_copy{
	test_copy(const test_copy& rhs){  ....  }
};

 

복사생성자는 같은 형태의 객체를 인자로 전달하는 시점에 호출된다.

1. 대입연산자 혹은 생성자의 인자로 전달하여 새로운 객체 초기화할 때

2. 함수 호출할 때, 인자로 넘기거나 반환하는 경우 (call by value)

클래스 구현에 복사생성자를 구현하지 않는다고 하더라도 컴파일러는 멤버 단위의 복사생성자를 자동으로 생성한다.
(default copy constructor).

복사생성자라는 개념에서 주의해야 하는 부분은 얕은 복사(swallow copy)로 인한 결과값의 오류이다.
아래의 예를 보자

class test_copy{
    ...
    test_copy(int n){ val = new int; }
    ~test_copy(){ delete val ; }
    void printIncInt() { cout << "int : " << ++(*val) << endl; 
    int * val;
};

void main()
{
    test_copy* a = new test_copy(2018); 
    a->printIncInt();      // 1

    test_copy b = *a;
    b.printIncInt();        // 2

    delete a;

    b.printIncInt();        // 3
}
 

a 객체가 2018값의 인자로 초기화될 때 정수 공간이 메모리에 할당된다.
첫 번째 출력은 포인터 변수 val이 가르키는 주소지의 값을 출력함으로 2019가 출력된다.
b 객체는 a를 전달하여 복사되는데, 이때 복사생성자를 정의하지 않았음으로 디폴트 복사 생성자가 호출된다.
a 객체의 멤버 val 주소를 복사하였음으로, 2020을 출력한다.
문제는 a객체를 소멸시킨뒤에 발생한다. a객체를 전달하여 b객체를 초기화할 때, 포인터 멤버의 주소를 단순복사하는데,
이 것은 단순히 하나의 메모리 주소를 공유하는 얕은 복사(swallow copy)가 된다.
a객체가 소멸되면서 두 객체에 의해서 공유되는 메모리가 해제되었기 때문에, b객체의 포인터 멤버가 가르키는 메모리는
소멸된 쓰레기값을 가르키게 될 것이다(Dangling). 따라서 3의 결과는 예외 발생과 함께 단순 쓰레기값의 출력이다.
이는 규모가 크고, 복잡한 솔루션에서 난해한 문제를 만들 수 있다(메모리를 깨먹는 곳과 다른... 엄한곳에서 죽기 마련이다).
이를 해결하기 위해서는 디폴트 복사생성자를 직접 정의하여, 초기화되는 객체의 새로운 메모리 영역을 잡고 값을 복사할 필요가 있다. 이를 깊은 복사라고 한다(deep copy)

class test_copy{
    ...
    test_copy(const test_copy& rhs) {
        this->val = new int(*rhs.val);
    }
    ...
};

복사생성자를 정의하고, 깊은 복사를 하도록 구현한뒤의 3의 결과는 오류없이 2021이 출력된다.
복사생성자에 대한 개념은 네이티브 개발자들이 반드시 알아야 하는 중요한 개념이다.
알아야 생각할 수 있다.

https://msdn.microsoft.com/ko-kr/library/87by589c.aspx

728x90