본문 바로가기

카테고리 없음

복사 생성자

복사 생성자는 흔히 메모리의 할당과 관련된다.

다음 예를 실행시키면 프로그램은 비정상 종료된다.

그 이유는 메모리 할당(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