[C++] 생성자(Constructor)와 소멸자(Destructor)

👻 들어가기에 앞서

  • 멤버 변수 네이밍

지난 시간에 클래스와 멤버 변수, 멤버 함수에 대해 알아보았다. 멤버 변수와 일반 변수는 차이가 없기 때문에 멤버 변수는 일종의 양식을 지정해주어 주로 사용한다.

변수 앞에 m을 붙이거나 언더바(_)를 붙여서 사용하는 게 보편적이다.

// 멤버 변수
int _hp;
int _attack;
int _posY;
int _posX;
  • this

멤버 함수 Die 내에 브레이크 포인트를 지정하고 디버깅을 실행시킨다음, 어셈블리어를 보면 다음과 같이 나온다.

Alt Text

_hp = 0 구문에서 this의 주소값을 eax에 넘겨주는데, 여기서 this는 무엇을 가리키는 걸까?

조금만 더 위로 올라가면 멤버 함수가 만들어지고 _hp = 0이 실행되기 전, ecx의 값을 this에 넣어주는 것을 확인할 수 있다. 여기서 ecx자기 자신의 주소를 가리키고 있다. 여기서는 Knight의 주소를 가리키고 있게 되는 것이다.

this->hp = 0;을 추가하여 비교해보자.

Alt Text

똑같이 동작하는 것을 알 수 있다. 메모리에 this를 입력하면 자기 자신이 나오는 것도 확인할 수 있다.


👻 생성자와 소멸자

클래스에 소속된 함수들을 멤버 함수라고 한다. 이 중에서 굉장히 특별한 함수 2종 이 있는데 바로 시작을 알리는 함수들이다. 시작을 알리는 함수생성자(Constructor), 끝을 알리는 함수소멸자(Destructor)라고 한다.

생성자는 여러개 존재가 가능하고 소멸자는 오로지 1개만 존재한다.


🌱 생성자(Constructor)

생성자는 여러 종류가 있다. 클래스 이름과 동일하게 만들어주면 된다. 객체가 생성될 때 자동으로 호출된다.

  • 기본 생성자 : 인자가 없다.
class Knight
{
public:
    Knight()
    {
        cout << "Knight() 기본 생성자 호출" << endl;

        _hp = 100;
        _attack = 10;
        _posY = 0;
        _posX = 0;
    }
}
  • 복사 생성자 : 자기 자신의 클래스 참조 타입을 인자로 받는다.
    class Knight
    {
    public:
      Knight(const Knight& knight)
      {
          _hp = knight._hp;
          _attack = knight._attack;
          _posY = knight._posY;
          _posX = knight._posX;
      }
    }
    

일반적으로 똑같은 데이터를 지닌 객체가 생성되길 기대한다. 복사 기능만 하기 때문에 99.99%로 const가 붙는다.

  • 기타 생성자 : 기본과 복사 외의 모든 생성자를 의미한다.
class Knight
{
public:
    Knight(int hp)
    {
        cout << "Knight(int) 생성자 호출" << endl;
        _hp = hp;
        _attack = 10;
        _posY = 0;
        _posX = 0;
    }
}

기타 생성자 중, 인자를 하나만 받는 기타 생성자를 타입 변환 생성자라 부르기도 한다.


🪐 암시적(Implicit) 생성자

  • 기본 생성자

생성자를 명시적으로 만들지 않으면 아무 인자도 받지 않는 기본 생성자컴파일러에 의해 자동으로 만들어진다.

그러나 우리가 명시적(Explicit)으로 아무 생성자를 하나 만들면, 자동으로 만들어지던 기본 생성자는 더 이상 만들어지지 않는다.

👉 클래스 내부에 생성자를 만들지 않으면 암시적으로 기본 생성자가 만들어지게 된다. 물론 내용은 비어있다.

  • 복사 생성자

또한 복사 생성자도 만들지 않으면 컴파일러에 의해 자동으로 만들어져 복사를 할 수 있게 해준다. 하지만 클래스를 생성할 때 선언 방법에 따라 어떤 생성자를 호출할지 나뉘어지게 된다.

// 생성함과 동시에 복사
Knight k2(k1);
Knight k3 = k1;

// 생성을 먼저한 후 복사
Knight k4;
k4 = k1;

위와 같이 선언했다고 가정하자. k2와 k3은 생성과 동시에 복사를 하라는 의미이므로 복사 생성자가 호출되게 된다. 반면에 k4는 생성을 먼저한 후에 k1의 값을 복사하라는 의미이므로 확인해보면 기본 생성자가 호출된다. 각 선언 방법에 따라 어떤 생성자를 호출할지 달라지게 되는 것이다.

  • 기타 생성자

기타 생성자 중 인자를 하나만 받는 기타 생성자를 타입 변환 생성자라 부르기도 한다고 했었다. 그 말인 즉슨, 알아서 형변환을 시켜준다는 의미인데 다음과 같은 코드가 있다고 가정해보자.

Knight k5;
k5 = 1;

메인 함수 내의 클래스 생성 부분이고, 오류가 뜰 것처럼 보인다. 하지만 정상적으로 잘 동작된다.

컴파일러가 알아서 k5의 _hp 값을 1로 세팅하라고 타입을 변환해서 클래스를 생성해주기 때문이다.

각각 브레이크 포인트를 잡고 확인해보면 Knight k5가 호출될 땐 기본 생성자가, k5 = 1가 호출될 땐 기타 생성자 Knight(int hp)가 호출된다. 암시적으로 컴파일러가 1이라는 값을 변환해서 넣어주게 되는 것이다. k5의 _hp 값을 살펴보면 1인 것을 확인할 수 있다.

  • explicit

이렇게 암시적으로 컴파일러가 알아서 값을 변환하고 세팅해주는 게 신경이 쓰인다면 explicit을 사용해 명시적인 용도로만 사용하도록 변경할 수 있다.

class Knight
{
public:
    // 명시적인 용도로만 사용할 것!
    explicit Knight(int hp)
    {
        cout << "Knight(int) 생성자 호출" << endl;
        _hp = hp;
        _attack = 10;
        _posY = 0;
        _posX = 0;
    }
}

🌱 소멸자(Destructor)

소멸자는 오로지 하나만 존재한다. 클래스 이름 앞에 물결표시(~)를 붙여 만들 수 있다.

class Knight
{
public:
    ~Knight()
    {
        cout << "Knight() 소멸자 호출" << endl;
    }
}

브레이크 포인트를 걸어 확인해보면, 클래스가 생성될 때 생성자가 호출되고 메인 함수가 종료될 때 소멸자가 호출된다.


👻 글을 마치며

이번 시간에는 생성자와 소멸자에 대해 알아보았다. 아주 얕은 지식으로 알고 있었던 부분인데 개념 정리가 확실하게 된 것 같다. 생각보다 많은 기능을 가지고 있어 다방면으로 활용하기 좋을 것 같다.


소스코드 보러가기


출처
인프런 Rookies님 강의

Categories:

Updated:

Leave a comment