복사 생성자는 흔히 메모리의 할당과 관련된다.
다음 예를 실행시키면 프로그램은 비정상 종료된다.
그 이유는 메모리 할당(new)을 한 번만 하고, 해제(delete)는 두 번 했기 때문이다.
그렇다면 왜 할당은 한 번, 해제는 두 번 발생했을까? 그 이유를 찾으면 복사 생성자가 필요한 이유를 알 수 있다.
#include <stdio.h>
#include
<string.h>
class CString
{
char
*data;
int
len;
public:
CString( const
char* string = ""
)
{
len = strlen( string );
data = new char[len+1];
}
~CString()
{
delete []
data;
}
};
void main( void
)
{
CString string1( "hello" );
// 생성자가 호출
CString string2 =
string1; // 생성자가 호출되지 않음
}
위 코드의 main() 함수 부분을 Assembly 코드로 보면 아래와 같다.
아래의 코드를 보면 생성자는 한 번 호출되며, 소멸자는 두 번 호출됨을 알 수 있다.
void main( void )
{
CString string1( "hello" );
0040105D push offset string "hello"
(0042501c)
00401062 lea ecx,[ebp-14h]
00401065 call
@ILT+0(CString::CString) (00401005) ; 생성자가
호출됨
0040106A mov dword ptr [ebp-4],0
CString string2 = string1;
00401071 mov
eax,dword ptr [ebp-14h] ; 생성자의 호출은 없음
00401074
mov dword ptr [ebp-1Ch],eax ; string2.data =
string1.data
00401077 mov ecx,dword ptr [ebp-10h]
0040107A
mov dword ptr [ebp-18h],ecx ; string2.len =
string1.len
26: }
0040107D lea
ecx,[ebp-1Ch]
00401080 call
@ILT+10(CString::~CString) (0040100f) ; 소멸자가
호출됨
00401085 mov dword ptr [ebp-4],0FFFFFFFFh
0040108C
lea ecx,[ebp-14h]
0040108F call
@ILT+10(CString::~CString) (0040100f) ; 소멸자가 호출됨
00401094
mov ecx,dword ptr [ebp-0Ch]
00401097 mov dword ptr
fs:[0],ecx
0040109E pop edi
0040109F pop
esi
004010A0 pop ebx
004010A1 add
esp,5Ch
004010A4 cmp ebp,esp
004010A6 call __chkesp
(00401590)
004010AB mov esp,ebp
004010AD pop
ebp
004010AE ret
자, 이러한 특성 때문에 메모리를 할당하는 클래스에는 반드시 복사 생성자(copy constructor)가 필요하다.
그럼 복사 생성자를 적용하여 완전한 클래스를 만들어 보자.
#include <stdio.h>
#include
<string.h>
class CString
{
char
*data;
int
len;
public:
CString( const
char* string = ""
)
{
len = strlen( string );
data = new char[len+1];
}
CString( const CString& string ) // 복사
생성자
{
len = string.len;
data = new char[len+1];
strcpy( data, string.data );
}
~CString()
{
delete []
data;
}
};
void main( void
)
{
CString string1( "hello" );
// 생성자가 호출
CString string2 =
string1; // 복사 생성자 호출
}
자 위와 같이 복사 생성자가 존재하는 경우 복사 생성자가 호출되어 프로그램은 별 무리없이 돌아간다.
아래 코드는 Assembly 코드로 확인해 본 것이다.
32: CString string1( "hello" );
0040105D push offset string "hello"
(0042501c)
00401062 lea ecx,[ebp-14h]
00401065 call
@ILT+0(CString::CString) (00401005) ; 생성자 호출 번지
0040106A
mov dword ptr [ebp-4],0
33: CString string2 = string1;
00401071 lea eax,[ebp-14h]
00401074 push
eax
00401075 lea ecx,[ebp-1Ch]
00401078 call
@ILT+15(CString::CString) (00401014) ; 복사 생성자 호출 번지
결과를 종합해 보면, 복사 생성자와 관련된 것은 다음과 같다.
첫째, 복사 생성자가 있으면 복사 생성자를 호출한다.
둘째, 복사 생성자가 없으면 멤버 대 멤버 복사(멤버 와이즈 복사)만 발생하며, 생성자는 호출되지 않는다.
셋째, 생성자가 호출되지 않았더라도 소멸자는 반드시 호출된다.
넷째, 명시적인 호출 (CString string2( string1 );) 또한 복사 생성자가 없을 때는 묵시적인 호출(CString string2 = string1;)
과 같다.
또한 복사 생성자가 호출되는 시점을 잘 알아야 한다. 복사 생성자는 다음과 같은 경우에 호출된다.
첫째, 객체가 다른 객체의 값이 대입될 때
둘째, 객체가 값에 의해 전달될 때
셋째, 객체가 값에 의해 리턴될 때
마지막으로 복사 생성자를 만들 때 주의해야 할 것이 있는데, 반드시 기본 생성자가 필요하다는 것이다.
기본 생성자(default constructor)란 매개 변수(인수)가 없는 생성자를 말한다.
펌: http://cafe.naver.com/pplus.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=149