포털:컴퓨터공학/C++/class: constructor and destructor

위키배움터

들어가기 전에 다음 소스를 보면서 필요성에 대해서 인식해 봅시다.

8.01 들어가기 전
#include <iostream><
include <cstring>

using namespace std;

class complex{
private :
 char * name
 int age;
public :
 void init(const char * input, const int num)
 {
  name = new char[strlen(input)+1];
  strcpy(name, input);
  age = num;
 }
 void view()
 {
  cout<<"name is "<<name<<", age is "<<age<<endl;
 }
 void ageUP(int num)
 {
  age = num;
 }
};

int main(void)
{
 complex Frank;
 Frank.init("Frank", 10);
 Frank.ageUP():


 complex Frodo;
 Frodo.init("Frodo", 20);
 Frodo.view()

 complex yang;
 yang.init("yang hyeon sik", 21);

 return 0;

}
name is Frodo, age is 20


위 8.01 소스에서 눈여겨 보셔야 할 부분은, complex 객체를 만들면 반드시 init함수를 호출해야 한다는 것입니다. init한 후 사용은 자유롭지만, init하지 않으면 멤버변수(char * name, int age)에 아무 값도 들어있지 않기 때문에 존재하나 마나한 객체가 되겠지요. 그렇다면 객체를 만들면 따로 명령하지 않아도 무조건 init함수를 호출하게 만들 순 없을까요?

늘 말하는 거지만, 만약 아주 긴 소스에서 init하지 않고 complex 객체를 사용한다면 문제가 발생할 수도 있을 것입니다.

여러분은 지금 생성자의 필요성에 대해서 들은 것입니다.


constructor[편집]

생성될 때 자동으로 호출되는 함수! 그것이 생성자입니다. 생성자에 대해서 다음 소스를 보면서 알아가 봅시다.

8.01 들어가기 전
#include <iostream>
#include <cstring>

using namespace std;

class complex{
private :
 char * name
 int age;
public :
 complex(const char * input, const int num)
 {
  name = new char[strlen(input)+1];
  strcpy(name, input);
  age = num;
  cout<<"call constructor"<<endl;
 }
 void view()
 {
  cout<<"name is "<<name<<", age is "<<age<<endl;
 }
 void ageUP(int num)
 {
  age = num;
 }
};

int main(void)
{
 complex Frank("Frank", 10);
 Frank.ageUP():


 complex Frodo("Frodo", 20);
 Frodo.view()

 complex yang("yang hyeon sik", 21);

 return 0;

}
call constructor
call constructor
name is Frodo, age is 20
call constructor

complex 객체를 만들때, name과 age에 들어갈 값을 같이 써줍니다.

소스 8.02를 다시 한번 살펴봅시다. name과 age를 초기화해주는 함수의 이름이 무엇이었습니까? 바로 complex였습니다! 객체 이름과 생성자 함수의 이름은 같다라는 것을 알 수 있습니다.

다음과 같이 생성자를 오버로딩시키는 것도 가능합니다. 8.01 소스와 유사하고 달라진 부분은 주석을 추가하였습니다.

8.02 오버로딩한 생성자
#include <iostream>
#include <cstring>

using namespace std;

class complex{
private :
 char * name
 int age;
public :
 complex(const char * input, const int num)
 {
  name = new char[strlen(input)+1];
  strcpy(name, input);
  age = num;
  cout<<"call constructor"<<endl;
 }
 complex(const char * input) // 새로운 부분
 {
  name = new char[strlen(input)+1];
  strcpy(name, input);
  age = 5;
 }
 void view()
 {
  cout<<"name is "<<name<<", age is "<<age<<endl;
 }
 void ageUP(int num)
 {
  age = num;
 }
};

int main(void)
{
 complex Frank("Frank", 10); 
 Frank.ageUP():


 complex Frodo("Frodo"); // 첫번째 인자만 써서 객체를 생성. complex(const char * input)함수가 실행된다.
 Frodo.view()

 complex yang("yang hyeon sik", 21);

 return 0;

}
call constructor
call constructor
name is Frodo, age is 5
call constructor

생성자를 오버로딩하였습니다. complex(const char * input, const int num)형태의 생성자와 complex(const char * input)형태의 생성자는 complex 객체의 선언 형태에 맞춰서 자동으로 알맞게 호출됩니다.

다음과 같이 객체를 선언 및 초기화 하는 것도 가능합니다. 8.02 소스와 유사하고 달라진 부분은 주석을 첨가하였습니다.

8.03 새로운 호출 방법
#include <iostream>
#include <cstring>

using namespace std;

class complex{
private :
 char * name
 int age;
public :
 complex(const char * input, const int num)
 {
  name = new char[strlen(input)+1];
  strcpy(name, input);
  age = num;
  cout<<"call constructor"<<endl;
 }
 complex(const char * input)
 {
  name = new char[strlen(input)+1];
  strcpy(name, input);
  age = 5;
 }
 void view()
 {
  cout<<"name is "<<name<<", age is "<<age<<endl;
 }
 void ageUP(int num)
 {
  age = num;
 }
};

int main(void)
{
 complex Frank = complex("Frank"); // 새롭게 사용한 객체 생성방식
 Frank.ageUP():


 complex Frodo("Frodo");
 Frodo.view()

 complex yang("yang hyeon sik", 21);

 return 0;

}
call constructor
call constructor
name is Frodo, age is 5
call constructor

complex Frank = complex("Frank")라는 것의 의미는 무엇일까요? 이 문장은 complex Frank("Frank")라는 문장과 완전히 동일합니다.

 T I P 
  생성자에 매개변수를 정의하는 부분은 있지만 리턴 타입을 정의하는 부분은 없다는 것을 눈치채셨을 것입니다.
  생성자 함수는 어떤 것도 리턴할 수 없습니다.


 예제 8.01번 
  학생 2명의 이름, 성적(국어, 영어, 수학)을 입력받고, 더 좋은 성적을 받은 학생을 뽑는 프로그램을 작성하세요.
  단, 생성자를 포함한 클래스를 정의하여 해결해야 합니다.

c++에서 모든 클래스는 자기만의 생성자를 가지고 있습니다. 하지만 학습자 여러분이 클래스를 처음 배웠을 때에는 생성자를 정의하지 않고도 잘 실행되는 소스를 작성할 수 있었습니다.

이것은 c++ 컴파일러가 생성자가 따로 정의되지 않은 클래스에 자동으로 default 생성자를 추가해주기때문입니다.

다음 소스를 보면서 이해해 봅시다.


8.04 디폴트 생성자
우리가 쓴 소스 자동으로 default 생성자가 추가된 소스
#include <iostream>
#include <cstring>

using namespace std;

class simple{
public :
 int num
};

int main(void)
{
 simple a;

 return 0;

}
#include <iostream>
#include <cstring>

using namespace std;

class simple{
public :
 int num
 simple(){}
};

int main(void)
{
 simple a;

 return 0;

}

소스 8.04에서 실행 전에 자동으로 simple()이라는 디폴트 생성자가 추가되었다는 것을 알 수 있습니다.

 주의하세요! 
  생성자 매개변수가 없는(void인) 객체를 초기화할 때 주의하세요. 다음과 같이 써서는 안됩니다.
/...class simple 정의 생략.../
int main(void)
{
 simple a();

 return 0;
}

  simple a()라는 문장은 a라는 객체를 simple(void)를 사용해 생성한다는 의미일 수도 있지만,
  동시에 return type이 simple이고 이름이 a이고, 매개변수가 void인 함수의 정의로도 해석할 수 있습니다.
  이 말이 이해가 가지 않는다면 깔끔하게 그냥 이렇게 쓰지 마세요!
  대신 이렇게 쓰도록 합시다.

/...class simple 정의 생략.../
int main(void)
{
 simple a;

 return 0;
}
 주의하세요! 
  프로그래머가 생성자를 정의하지 않으면 → default 생성자가 컴파일시 자동으로 생깁니다
  프로그래머가 생성자를 정의하면 → default 생성자는 생기지 않습니다.

destructor[편집]

8.05 동적 할당을 하는 생성자를 이용하는 객체
#include <iostream>
#include <cstring>

class boy{
private :
 char * name;
public :
 boy(const char * input)
 {
  name = new char[strlen(input)+1];
  strcpy(name,input);
 }
 void viewName()
 {
  cout<<name<<endl;
 }
};

int main(void)
{
 boy TaeHyeon("my name is TaeHyoen");
 TaeHyeon.viewName();

 return 0;
}
my name is TaeHyeon


동적할당을 하여 이름을 받아들이고 있습니다.

"동적할당"에서 new를 한후 delete를 해야 한다는 것이 기억나시나요? delete를 하지 않은 이 소스는 잘못된 소스입니다!

우리가 해야 할 일은 delete해주는 부분을 작성하는 것입니다. void delete(void)라는 함수를 작성해야 겠네요.

하지만 만약 사용자가 delete(void)라는 함수를 호출하지 않으면 어쩌죠?

비슷한 이유 때문에 생성자 존재했었습니다. 사용이 끝난 객체의, 자동적으로 호출되는 delete가 바로 소멸자입니다.

다음 소스를 보면서 소멸자를 어떻게 사용하는지 확인해봅시다.

8.06 소멸자를 사용한
#include <iostream>
#include <cstring>

class boy{
private :
 char * name;
public :
 boy(const char * input)
 {
  name = new char[strlen(input)+1];
  strcpy(name,input);
 }
 void viewName()
 {
  cout<<name<<endl;
 }
 ~boy()
 {
  cout<<"call destructor"<<endl;
  delete [] name;
 }
};

int main(void)
{
 boy TaeHyeon("my name is TaeHyoen");
 TaeHyeon.viewName();

 return 0;
}
my name is TaeHyeon
call destructor


~boy()라고 정의되어 있는 함수가 바로 소멸자입니다. 소멸자는 매개변수와 리턴형에 대한 정의가 불가능합니다

따라서 매개변수를 전달해 줄 수도 없고 어떤 값을 리턴시킬 수도 없습니다.

 T I P 
  일반적으로 소멸자는 프로그램이 종료되기 바로 직전에 자동으로 실행됩니다.
  하지만 다음처럼 프로그래머가 작동시킬 수도 있습니다.
/*....클래스의 정의 생략....*/
int main(void)
{
 cmplex one;
 one.~one();

 return 0;
}

  덤으로 생성자 또한 위처럼 호출할 수 있다는 것도 알아두세요!


 연습 문제 8.01번 
  학생의 이름, 성적(국어, 수학, 영어)를 입력받고 평균을 출력하는 프로그램을 작성하시오.
  단, 모든 변수는 동적할당하시오.

정리[편집]

이번 강의에서 배웠던 것을 기억해보세요. 복습은 최고의 공부법!

  1. 객체를 생성할때 자동으로 호출되는 함수! 바로 생성자
  2. 우리가 따로 정의하지 않으면 default 생성자가 자동으로 추가되고, 정의하면 default 생성자는 사라진다.
  3. 주의하세요! 생정자 매개변수가 void라고 simple a()처럼 객체를 생성해서는 안됩니다!
  4. 객체가 소멸될때 자동으로 호출되는 함수! 바로 소멸자!