-
디폴트 복사생성자의 단점카테고리 없음 2020. 4. 7. 00:12
복사생성자의 단점
복사생성자는 디폴트 생성자와 같이 Class를 만들 때 직접 정의하지 않아도 컴파일러가 알아서 디폴트 복사생성자를 생성해 준다. 하지만 이런 디폴트 복사 생성자는 한가지 단점을 가지고 있는데 그것이 바로 디폴트 복사 생성자는 얕은 복사를 한다는 점이다. 그렇다면 얕은 복사란 무엇일까 ?
얕은 복사
// HelloNew_C+.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다. // #include "stdafx.h" #include<iostream> #include<stdio.h> using namespace std; class Ctest { public: // 디폴트 생성자 Ctest(int n){ p_A = new int; *p_A = n; } // 복사 생성자 Ctest(Ctest &rhs){ this->p_A = rhs.p_A; } // p_A의 값 즉 주소를 출력 void read_func(){ cout << this->p_A << endl; } private: int* p_A; }; int _tmain(int argc, _TCHAR* argv[]){ Ctest a(10); Ctest b(a); a.read_func(); b.read_func(); return 0; }
위의 자료는 얕은 복사를 한 경우이다.
멤버 요소로 포인터 p_A 가 있으며 객체 a가 생성되며 포인터 p_A에 메모리를 동적할당 하며 동적할당한 메모리에 매개변수로 받은 값을 넣은 것이다. 이때 객체 a에 대한 복사 객체인 b를 생성하게 되면 당연히 복사 생성자가 불릴 것이고 복사 생성자는 this->p_A (객체 자신의 멤버) 에 매개변수로 넘어본 복사할 대상인 객체 a의 멤버 (rhs.p_A) 의 값을 할당하게 될 것이다. 따라서 서로 다음과 같은 관계가 성립된다.
디폴트 복사 생성자는 위와 같이 매개변수로 전달된 객체 a의 p_A 포인터의 값 즉 주소를 복사해 버린다. 따라서 b라는 객체의 p_A 멤버는 객체 a의 멤버 포인터인 p_A의 주소를 복사하게 된다. 이와 같이 주소를 복사해 버린다면 메모리를 동적 해제할 시 객체 a, b 상관 없이 하나의 객체에서 멤버 p_A를 해제한다면 자동으로 다른 객체에 해당하는 멤버 p_A는 자신이 가리키고 있던 메모리의 주소를 읽게 된다. 따라서 동적해제 과정에서 유효하지 않은 메모리를 해제함으로 오류가 발생하게 된다.
깊은 복사
이제 얕은 복사와 얕은 복사가 가지는 문제점에 대해서 알았을 것이다. 따라서 만약 Class 내부에서 메모리를 동적할당하고 동적할당한 메모리를 포인터를 통해서 관리할 경우 깊은 복사를 통해 동적할당한 메모리를 관리하여야한다. 그렇다면 깊은 복사란 무엇일까 ?
// HelloNew_C+.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다. // #include "stdafx.h" #include<iostream> #include<stdio.h> using namespace std; class Ctest { public: // 디폴트 생성자 Ctest(int n){ p_A = new int; *p_A = n; } ~Ctest(){ delete p_A; } // 복사 생성자 // 딥 카피 진행 Ctest(Ctest &rhs){ this->p_A = new int; *(this->p_A) = *(rhs.p_A); } void read_func(){ cout << this->p_A << endl; } private: int nData; int* p_A; }; int _tmain(int argc, _TCHAR* argv[]){ Ctest a(10); Ctest b(a); a.read_func(); b.read_func(); return 0; }
얕은 복사 코드와 위의 코드를 비교해 보자 복사 생성자 부분이 달라진 것을 확인할 수 있다.
얕은 복사에서는 매개변수로 넘어온 객체의 p_A 포인터 값 즉 주소를 this->p_A 에 단순 대입한다. 하지만 깊은 복사에서는 매개변수로 넘어온 객체의 p_A 포인터 간접지정 연산 결과
즉 p_A 포인터의 값인 주소의 메모리에 있는 자료를 복사하여 this 포인터 대상의 멤버인 p_A의 간접지정 연살 결과에 단순 대입한다. *(this->p_A) 따라서 thist -> p_A의 값인 메모리에 복사한 값이 들어가게 된다.
위와 같이 객체 a와 b의 멤버 p_A는 서로 다른 메모리를 동적할당 받으며 동적할당 받은 메모리의 값으로 서로 같은 값을 가진다 따라서 p_A는 값만 같을 뿐 서로 각자의 메모리를 가지고 있다.