본문 바로가기
Develop/C++

[C ++] 생성자와 소멸자

by J-rain 2023. 2. 3.

'생성자'와 '소멸자'는 클래스 객체가 생성 및 소멸될 때 '자동으로' 호출되는 함수이다. 이 두 함수의 특징은 함수임에도 '반환 형식이 없다'는 것과 함수 이름이 클래스 이름과 같다. 다만 소멸자의 이름 앞에는 ~가 붙는다.

클래스이름();
~클래스이름();

매개변수가 하나도 없는 생성자를 '디폴트 생성자' 라고 한다. 클래스 제작자가 디폴트 생성자와 소멸자를 기술하지 않아도 컴파일러가 알아서 만들어 넣는다는 점 즉, '생성자와 소멸자가 없는 클래스는 없다'를 기억하자

#include <iostream>
using namespace std;

class RAIN
{
 public :
 RAIN()   // 생성자
 {
  cout << "RAIN::RAIN()" << endl;
 }
 
 ~RAIN()  // 소멸자
 {
  cout << "RAIN::~RAIN()" << endl;
 }
};

int main(void)
{
  cout << "시작" << endl;
  RAIN r;
  cout << "끝" << endl;
  
 return 0;
 }

실행결과를 보기전에 어떻게 출력될지 예상해보자

 

 

 

 

실행결과

아마 실행 결과를 잘못 예측했다면 대부분 '끝'이 출력되기 전에 소멸자가 호출될 것으로 예측했을 가능성이 높다.

중요한 점은 21번행에서 인스턴스 r을 선언했고 이 객체가 생성되는 시점이라는 것과 r이 main() 함수에 속한 지역 변수라는 점이다. 지역 변수는 특성상 선언된 블록 범위가 끝나면 자동으로 소멸한다. 따라서 main() 함수의 실행이 끝나면 r은 소멸한다.

    // 위의 코드를 이와같이 수정하면 결과는 어떻게 될지 생각해보자
17  RAIN r;
18  int main(void)
19  {
20   cout << "시작" << endl;
21   cout << "끝"  << endl;
22
23  return 0;
24 }

실행결과

이와같이 흥미로운 결과가 나왔다. "C로 작성한 프로그램에서 가장 먼저 호출되는 함수는 main() 함수다"라는 명제가 깨졌기 때문이다. C++ 에서는 전역 변수로 선언한 클래스의 생성자가 main() 함수보다 먼저 호출된다.

 


위의 내용을 숙지하고 풀어보자

#include <iostream>
using namespace std;

class RAIN
{
 public :
 RAIN()   // 생성자
 {
  cout << "RAIN::RAIN()" << endl;
 }
 
 ~RAIN()  // 소멸자
 {
  cout << "RAIN::~RAIN()" << endl;
 }
};

int main(void)
{
  cout << "시작" << endl;
  RAIN r;
  cout << "중간" << endl;
  RAIN rr;
  cout << "끝" << endl;
  
 return 0;
 }

 

 

실행결과

이처럼 main() 함수의 마지막 구문이 실행된 후 내부에 선언된 지역 변수들이 소멸된 것을 확인할 수 있다.

정리하자면

  • main ( ) 함수가 호출되기 전에 생성자가 호출될 수 있다.
  • 생성자는 다중 정의할 수 있다.
  • 소멸자는 다중 정의할 수 없다.
  • main ( ) 함수가 끝난 후에 소멸자가 호출될 수 있다.
  • 생성자와 소멸자는 생략할 수 있으나 생략할 경우 컴파일러가 만들어 넣는다.

새로운 생성자를 만들면서 디폴트 생성자를 생략할 수 있다. 앞서 말한 내용으로 '디폴트 생성자'는 매개변수가 없는 생성자이다.

#include <iostream>
using namespace std;

class RAIN
{
  int Data;

 public :
   // 생성자의 매개변수로 전달된 값으로 멤버 변수를 초기화한다.
   RAIN(int Param) : Data(Param)       // 생성자
   {
     cout << "RAIN::RAIN()" << endl;
   }
   
   ~RAIN()                            // 소멸자
   {
     // 생성할 때 매개변수로 받은 값을 출력한다.
     cout << "RAIN::~RAIN()" << Data << endl;
    }
 };
 
 int main(void)
 {
   cout << "시작" << endl;
   RAIN r(1);                // 생성자 호출
   cout << "중간" << endl;
   RAIN rr(2);               // 생성자 호출
   cout << "끝" << endl;
   
 return 0;
 }

실행결과


생성자 다중 정의

생성자가 다중 정의될 경우 사용자는 편하겠지만 제작자는 동일한 멤버를 초기화하는 코드를 여러번 기술해야 하는 번거러움이 있었다. 하지만 C++11 표준부터 '생성자 위임'이 지원되어 이러한 번거러움을 해소하게 되었다.

#include <iostream>
using namespace std;

class RAIN
{
 public :
  RAIN(int x)
  {
    cout << "RAIN(int)" << endl;
    // x 값이 100 이 넘는지 검사하고 넘으면 100으로 맞춘다.
    if(x > 100)
      x = 100;
      
    r_x = 100;
  }
  
  RAIN(int x, int y)
    // x 값을 검사하는 코드는 이미 존재하므로 재사용한다. (7번행)호출
    : RAIN(x)
    {
      cout << "RAIN(int,int)" << endl;
      
      // y 값이 200이 넘는지 검사하고 넘으면 200으로 맞춘다.
      if(y > 200)
        y = 200;
        
      r_y = 200;
     }
     
     void Print()
     {
       cout << "x:" << r_x << endl;
       cout << "y:" << r_y << endl;
     }
     
  private :
    int r_x = 0;
    int r_y = 0;
};

int main(void)
{
 // 매개변수가 하나인 생성자만 호출한다.
 RAIN r(110);
 r.Print();
 
 // 이번에는 두 생성자 모두 호출한다.
 RAIN rr(50, 250);
 rr.Print();
 
return 0;
}

 

실행결과

19번행에서 RAIN(int,int) 생성자는 초기화 목록에서 RAIN(int) 생성자가 추가로 호출될 수 있도록 (재사용할 수 있도록) 위임했다.

 


동적 객체의 생성과 소멸

new와 delete 연산자는 각각 생성자와 소멸자를 호출한다.

#include <iostream>
using namespace std;

class RAIN
{
  int Data;
 
 public :
   RAIN()
   {
     cout << "RAIN::RAIN()" << endl;
   }
   
   ~RAIN()
   {
     cout << "RAIN::~RAIN()" << endl;
   }
 };
 
 int main(void)
 {
   cout << "시작" << endl;
   
   // new 연산자를 이용해 동적으로 객체를 생성한다.
   RAIN *pData = new RAIN;
   cout << "RAIN" << endl;
   
   // delete 연산자를 이용해 객체를 삭제한다.
   delete pData;
   cout << "끝" << endl;
   
   return 0;
}

실행결과

delete때문에 main()함수가 끝나기전에 소멸자가 호출되었다.

객체를 동적으로 생성할 때 한 가지 주의할 점은 '배열로 생성한 객체들은 반드시 배열로 삭제'해야 한다.

20 int main(void)
21 {
22    cout << "시작" << endl;
23 
24   // 배열로 new 연산을 수행할 수 있다.
25  RAIN *pData = new RAIN[3];
26
27  // 배열로 생성한 것은 배열로 삭제한다!
28  delete [ ] pData;
29  cout << "끝" << endl;
30
31  return 0;
32 }
이와 같이 수정하고 결과를 확인해보자

실행결과

객체를 배열로 생성했으니 배열로 삭제하는 것이 당연하다. 만약 배열로 삭제하지 않을 경우

28  delete pData;

실행결과

메모리 릭이 발생하여 버그가 생겼다.

한 가지 다행스러운 것은 클래스가 아니라 char나 int 같은 기본 자료형의 배열은 배열로 삭제하지 않는다고 하더라도 오류는 발생하지 않는다.

'Develop > C++' 카테고리의 다른 글

[C ++] 복사 생성자  (0) 2023.02.04
[C ++] 상수형 메소드  (0) 2023.02.03
[C ++] 접근 제어 지시자  (0) 2023.02.03
[C ++] 클래스 기본 문법  (0) 2023.02.03
[C ++] Namespace 와 using  (0) 2023.02.02

댓글