2.생성자 와 소멸자

본장에서는 클래스를 가 만들어질 때 실행되는 함수와 종료 될 경우 실행되는 함수에 대해서 설명합니다. C++에서는 클래스가 만들어질 때 자동적으로 실행되는 함수가 있으며 또한 클래스 사용이 끝났을 때 자동적으로 실행되는 함수가 있습니다. 본장을 통해서 이 두함수의 사용법을 배우며 클래스 변수를 매모리에 설정하는 방법에 대해서도 배우시게 될것입니다.

생성자 와 소멸자의 필요성

객체 지향 방식의 언어는 자연구조 또는 인간의 생활 구조와 비슷한 형식으로 설정된 것이 특징입니다. 그렇기 때문에 생성자와 소멸자가 필요한 것입니다. 일반인들은 아침에 일어나면 자동적으로 하는 것이 있습니다. □세수하고 밥먹고 옷입고 등등□ 이것은 기본적으로 합니다. 물론 바빠서 이것도 안하고 출근을 하거나 등교를 하는 경우가 있지만 보통은 기본적으로 하게 됩니다. 또한 하루 일과가 끝나면 씻고 옷갈아입고 그리고 잡니다. 이것도 기본적으로 합니다. 하다못해 데이트를 할 때도 선처리 작업으로 폼내고,지갑채우고 등등을 하며 데이트가 끝나면 결과를 정리하고 다음에 만날것인가 안만날것인가를 결정합니다. 즉 어떤 작업이 진행되기 전에는 보통 처음에 해야할 일이 있고  작업이 끝나면 정리할 일이 있는것입니다. 클래스에서도 이런 기능을 제공합니다. 본 클래스를 사용할 때 처음에 기본적으로 해야할 일이 있으며 이클래스가 사라질 때 기본적으로 해야할 일이 있는것입니다. C++에서는 처음에 기본적으로 해야할 일을 정의해놓는 함수가 있습니다. 이것을 생성자라고 하며 함수는 클래스 명과 같습니다. 다음은 생성자의 예입니다.

class CNamecard{

:

public:

CNamecard();//생성자

:

}

CNamecard::CNamecard()

{

: //이곳에 시작할 때 필요한 내용을 기록한다.

}

CNamecard와 같은 함수 CNamecard()라는 형을 만든 것이 바로 CNamecard의 생성자입니다.

소멸자는 클래스 이름에  □~□ 기호를 붙인것입니다. 다음은 소멸자의 예입니다.

class CNamecard{

:

public:

~CNamecard();//소멸자

:

}

CNamecard::~CNamecard()

{

://이곳에 소멸할 때 필요한 내용을 기록한다.

}

CNamecard에 CNamecard라는 생성자와 ~CNamecard라는 소멸자를 설정하면 클래스가 시작되면서 바로 CNamecard()함수가 실행되면 클래스가 사라지면 ~CNamecard가 실행된후에 사라지게 됩니다.

생성자 와 소멸자의 실행 싯점

생성자의 실행 시점은 설정한 순간입니다. 예를 들어 위의 CNamecard를 다음과 같이 설정을 하면

CNamecard m_card;

위의 선언 자체에서 바로 CNamecard()함수가 실행되는 것입니다.

소멸자에서는 약간 궁금할것입니다. 과연 언제 실행될것인가? 예를 들어 다음과 같이 정의 하고 수행을 하였다고 가정을 한다면 어디 부분에서 소멸자가 실행될것인가를 생각해 보시기 바랍니다.

void main()

{

CNamecard m_card;

m_card.SetName("이상엽□);

m_cardSetTel("111-1111");

m_cardSetAddress("서울□);

}

위의 예라면 CNamecard형인 m_card의 소멸자 함수 ~CNamecard는 mian()이 종료되는 시점에서 실행되게 됩니다. 포인터를 상요하지 않고 정적 변수로 사용하였을경우에는 설정된 함수가 종료될 때 수행이됩니다. 만일 다음과 같이 namecontrol()이라는 함수에서 CNamecard를 설정하였다면 namecontrol함수가 종료되면서 m_card의 소멸자도 실행되게 됩니다.

void namecontrol()

{

CNamecard m_card;

m_card.SetName("이상엽□);

m_cardSetTel("111-1111");

m_cardSetAddress("서울□);

}

void main()

{

namecontrol(); //본함수가 종료되면서 m_card의 소멸자 실행

}

만일 namecontrol()안에 CNamecard를 static형태로 설정하였다면 어떻게 될까요?

void namecontrol()

{

static CNamecard m_card;

m_card.SetName("이상엽□);

m_cardSetTel("111-1111");

m_cardSetAddress("서울□);

}

void main()

{

namecontrol(); //본함수가 종료되면서 m_card의 소멸자 실행

}

static로 정의하면 메모리에 설정되기 때문에 namecontrol()함수의 수행이 종료되어도 m_card는 종료 되지 않습니다. 이때에는 main함수가 종료 되었을 때 즉 프로그램이 종료 되는순간에 소멸자가 종료 됩니다.

C++에서의 메모리 관리

C++에서 메모리에 데이터를 설정할때는 new라는 연산자와 메모리에서 데이터를 해제 할경우에는 delete라는 연산자를 사용합니다.

다음 예는 char* 형태로 포인터를 설정한후에 512크기의 메모리를 설정하는 예입니다.

char *p;

p=new char[512];

:

delete p;

new다음에 설정할 형을 써주고 그형의 배열을 설정하면 그 크기만큼 매모리에 할당됩니다. 이렇게 할당한후에 이것을 해제 할 때는 delete함수를 사용하면 됩니다.

다음 은 int형의 포인터로 52개의 메모리를 설정한 예입니다.

int *p;

p=new int[52];

:

delete p;

C++에서의 메모리설정은 C에서보다 강력하며 매우 편리하게 되어있다는 것을 느낄수 있을것입니다. 클래스 변수 또한 new연산자를 이용하여 메모리에 할당할수 있습니다.

CNamecard *p;

p=new CNamecard;

p->SetName("이상엽□);

delete p;

지금까지는 CNamecard를 정적 변수로 사용하였습니다. 이번에는 이것을 포인터로 사용한것입니다. 포인터로 사용할경우에는 이 포인터에 할당할 메모리가 필요하며 이메모리를 할당하기 위해서 new연산자를 이용한것입니다.

다시 생성자와 소멸자로 넘어가서 클래스에서는 new연산자가 실행될 때 생성자가 실행됩니다. 또한 delete가 실행될 때 소멸자가 실행됩니다. 보통 생성자에는 메모리에 특정 데이터를 설정하거나 값을 초기화 할 때 사용되며 소멸자는 데이터를 해제할 때 사용합니다. CNamecard의 name를 포인터로 설정하였을 때 생성자와 소멸자에 다음과 같은 방법을 이용하여 메모리를 name에 할당하고 해제 할수 있습니다.

class CNamecard{

protected:

char *name;

:

public:

CNamecard();

~CNamecard();

:

}

CNamecard::CNamecard()

{

name=new char[80]; //메모리를 할당한다.

}

CNamecard::~CNamecard()

{

delete name;

}

일반적으로 클래스안에 변수들을 정적으로 사용하기 보다 동적으로 메모리를 할당하여 사용하는경우가 많습니다. 위의예는 name라는 맴버 변수를 포인터로 설정하고 클래스가 생성될 때 name에 메모리를 할당하고 소멸할 때 name의 할당을 해제한것입니다.

생성자와 소멸자는 C에서 문제가 되었던 메모리 관리 문제에 많은 해결책을 주었습니다. 프로그램을 작성하다 보면 필요한 데이터를 로드하기위해 메모리를 할당해주고 실수로 또는 어쩔수없이 메모리를 해제 하지 못하는 경우가 많습니다. 어쩔수 없이 메모리를 해제 할수 없는 경우는 해제하고자 하는 위치를 찾지 못할경우입니다. 예를 들어서 함수내에 static변수로 설정하고 이데이터에 메모리를 할당하였다면 이것을 외부에서 해제하기에는 매우 힘들기 때문입니다. 이런 문제로 프로그램을 오래 사용하다 보면 메모리 부족으로 프로그램의 가동 중단이 발생하는 경우가 생길때도 있었습니다. 그러나 C++의 클래스를 사용하였다면 소멸자에 설정하면 프로그래머가 일일이 메모리 해제를 하지 않아도 자동적으로 소멸이 됩니다.

부탁드릴 것은 클래스를 만들경우에 우선적으로는 생성자와 소멸자를 만드는 일을 게을리 하지 말것이며 그리고 메모리 설정과 해제는 주로 생성자와 소멸자에서 해주시기를 바랍니다.  생성자에서 설정을 하는 것은 꼭 강요하고 싶지는 않습니다. 그러나 소멸자에서 메모리 해제는 꼭 필요한것입니다.

보통 일반적인 방법은 생성자에서 메모리를 설정하는 것 보다 어떤 초기화 함수를 만들고 이함수를 실행하면서 메모리를 설정하는 경우가 많습니다. 다음은 초기화 함수를 만들고 이곳에서 메모리를 설정한 예입니다.

class CNamecard

{

protected:

char *name;

:

public:

CNamecard();

~CNamecard();

int InitName(int size);

};

CNamecard::CNamecard()

{

name=NULL;

}

CNamecard::~CNamecard()

{

if(name!=NULL)

delete name;

}

int CNamecard::InitName(int size);

{

name= new char[size];

if(name==NULL)

return 0;

return 1;

}

void main()

{

CNamecard *p=new CNamecard;

p->InitName(80);//name에 80크기의 메모리를 할당한다.

delete p;

}

위의 예에서 보면 생성자에서는 name에 NULL이라는 값만 설정되었습니다. 그리고 메모리 필요에 따라서 InitName함수에서 설정한 값만큼 name에 메모리가 할당됩니다. 이렇게 프로그램 중간에 필요에 의해서 맴버 변수에 메모리를 할당한다 하여도 소멸자에서는 이것을 확인하고 메모리가 할당되었는가를 확인하여 할당되었으면 해제해주는 것이 필요합니다.

소멸자 ~CNamecard()함수에서 if(name!=NULL) 이라는 문장에서 중간에 InitName함수에 의해서 메모리가 할당되었는지 아닌지를 확인할 수가 있습니다. 그이유는 생성자에서 name에 NULL를 할당하였고 InitName가 수행되지 않는한 name는 NULL이기 때문입니다. 이문장은 if(name) 로 바꾸셔도 같은 의미가 됩니다.

이 조건문을 이용하여 name에 메모리가 할당되었다면 바로 메모리를 해제하여 줍니다. 이와 같이 소멸자에 메모리를 해제하는 루틴은 C++에서 class를 사용하는 기본적인 방법입니다.

C++에서의 메모리 관리 예제 CpExam01

CpExam01은 CNamecard의 클래스의 name,tel,address를 포인터로 설정하고 InitData함수를 실행시에 각 맴버 변수 포인터에 메모리를 할당하는 방법을 이용항 예입니다. 본예제에서 소멸자는 name,tel,address에 메모리가 할당되었으면 해제 하는 루틴이 들어 있습니다.

(프로그램 소스)

#include <stdio.h>

#include <string.h>

class CNamecard{

protected:

char *name;

char *tel;

char *address;

public:

CNamecard();//생성자

~CNamecard();//소멸자

void InitData(int nsize,int tsize,int asize);//메모리 설정 함수

void SetName(char* na);

char *GetName();

void SetTel(char* te);

char *GetTel();

void SetAddress(char *ad);

char *GetAddress();

};

//생성자 함수

CNamecard::CNamecard()

{

//메모리 포인터를 모두 NULL로 설정한다.

name=NULL;

tel=NULL;

address=NULL;

}

//소멸자

CNamecard::~CNamecard()

{

if(name)

delete name;

if(tel)

delete tel;

if(address)

delete address;

printf("클래스 종료\n");

}

//메모리 할당

void CNamecard::InitData(int nsize,int tsize,int asize)

{

name=new char[nsize];

tel=new char[tsize];

address=new char[asize];

}

void CNamecard::SetName(char *na)

{

strcpy(name,na);

}

void CNamecard::SetTel(char* te)

{

strcpy(tel,te);

}

void CNamecard::SetAddress(char* ad)

{

strcpy(address,ad);

}

char *CNamecard::GetName()

{

return name;

}

char *CNamecard::GetTel()

{

return tel;

}

char *CNamecard::GetAddress()

{

return address;

}

void namecontrol()

{

static CNamecard m_card;

m_card.SetName("이상엽");

}

void main()

{

namecontrol();

char temp[80];

CNamecard *m_card;

m_card=new CNamecard;

m_card->InitData(80,20,80);//메모리 설정

printf("이름:");

gets(temp);

m_card->SetName(temp);

printf("전화번호:");

gets(temp);

m_card->SetTel(temp);

printf("주소:");

gets(temp);

m_card->SetAddress(temp);

printf("이름:%s\n",m_card->GetName());

printf("전화번호:%s\n",m_card->GetTel());

printf("주소:%s\n",m_card->GetAddress());

gets(temp);

delete m_card;

}

(프로그램 소스끝)

CpExam01를 제작할 때 프로젝트에서 Settings를 바꾸어주어야 합니다. 메뉴에서 Project항목에서 Settings를 선택하고 카드철 메뉴에서 Link를 선택한다음 Object/library modules 의 항목에 libc.lib /nodefaultlib를 세롭게 삽입하여야 합니다.

그림 1은 프로젝트의 Settings에서 Link에 libc.lib /nodefaultlib를 설정하는 화면입니다.

(그림 1) CpExam01 프로젝트 Settings화면

앞으로 제작하는 DOS모드의 프로그램은 위의 Settings를 꼭 설정해주어야 합니다. 메모리 설정을 하는 부분에서 이옵션을 설정하지 않으면 구동에 문제가 있을것입니다.

그림 2는 CpExam01의 출력 결과입니다.

(그림 2) CpExam01출력 결과

생성자에 인자 넘겨주기

때로는 생성자를 만들어 주면서 인자를 넘겨줄때도 있습니다. 예를 들어서 CpExam01의 InitData함수에서 메모릴 할당하는 것을 생성자에 설정할수도 있다는 것입니다. CNamecard의 생성자에 인자를 설정하는 예는 다음과 같습니다.

class CNamecard{

protected:

char *name;

char *tel;

char *address;

public:

CNamecard(int nsize,int tsize,int asize);//생성자

:

};

//생성자 함수

CNamecard::CNamecard(int nsize,int tsize,int asize)

{

name=new char[nsize];

tel=new char[tsize];

address=new char[asize];

}

위의 예는 CNamecard생성자에 InitData함수를 합친 것 같은 느낌을 줄것입니다.

생성자는 생성자 함수를 만들 때 인자를 설정할 수가 있습니다. CpExam01에서 보변 실제  name,tel,address에 메모리를 할당하는 것을 생성자에서 하는것도 나쁘지 않는 방법입니다. 이럴 경우 생성자에 인자를 두어서 설정할수 있는 것입니다. 위의생성자를 이용할때는 다음과 같이합니다.

CNamecard *m_card;

m_card=new CNamecard(80,20,80);

CNamecard(80,20,80); 으로 설정하면 name에 80만큼,tel에 20만큼 address에 80만큼의 메모리가 설정됩니다.

생성자는 인자를 넘겨줄수 있느나 소멸자는 인자를 넘겨줄수 없습니다. 따라서 소멸자는 꼭 ~CNamecard(); 형태로 등록되어야 합니다.

CpExam02는 생성자에 인자를 넘겨주는 형식의 예제입니다.

실제 프로그램을 직접 작성해 보시기 바랍니다.

(프로그램 소스)

#include <stdio.h>

#include <string.h>

class CNamecard{

protected:

char *name;

char *tel;

char *address;

public:

CNamecard(int nsize,int tsize,int asize);//생성자

~CNamecard();//소멸자

void SetName(char* na);

char *GetName();

void SetTel(char* te);

char *GetTel();

void SetAddress(char *ad);

char *GetAddress();

};

//생성자 함수

CNamecard::CNamecard(int nsize,int tsize,int asize)

{

name=new char[nsize];

tel=new char[tsize];

address=new char[asize];

}

//소멸자

CNamecard::~CNamecard()

{

if(name)

delete name;

if(tel)

delete tel;

if(address)

delete address;

printf("클래스 종료\n");

}

void CNamecard::SetName(char *na)

{

strcpy(name,na);

}

void CNamecard::SetTel(char* te)

{

strcpy(tel,te);

}

void CNamecard::SetAddress(char* ad)

{

strcpy(address,ad);

}

char *CNamecard::GetName()

{

return name;

}

char *CNamecard::GetTel()

{

return tel;

}

char *CNamecard::GetAddress()

{

return address;

}

void main()

{

char temp[80];

CNamecard *m_card;

m_card=new CNamecard(80,20,80);

printf("이름:");

gets(temp);

m_card->SetName(temp);

printf("전화번호:");

gets(temp);

m_card->SetTel(temp);

printf("주소:");

gets(temp);

m_card->SetAddress(temp);

printf("이름:%s\n",m_card->GetName());

printf("전화번호:%s\n",m_card->GetTel());

printf("주소:%s\n",m_card->GetAddress());

gets(temp);

delete m_card;

}

(프로그램 소스끝)