[C++] 생성자(Constructor)와 소멸자(Destructor)
👻 들어가기에 앞서
- 멤버 변수 네이밍
지난 시간에 클래스와 멤버 변수, 멤버 함수에 대해 알아보았다. 멤버 변수와 일반 변수는 차이가 없기 때문에 멤버 변수는 일종의 양식을 지정해주어 주로 사용한다.
변수 앞에 m
을 붙이거나 언더바(_)
를 붙여서 사용하는 게 보편적이다.
// 멤버 변수
int _hp;
int _attack;
int _posY;
int _posX;
- this
멤버 함수 Die
내에 브레이크 포인트를 지정하고 디버깅을 실행시킨다음, 어셈블리어를 보면 다음과 같이 나온다.
_hp = 0
구문에서 this
의 주소값을 eax
에 넘겨주는데, 여기서 this
는 무엇을 가리키는 걸까?
조금만 더 위로 올라가면 멤버 함수가 만들어지고 _hp = 0
이 실행되기 전, ecx
의 값을 this
에 넣어주는 것을 확인할 수 있다. 여기서 ecx
는 자기 자신의 주소를 가리키고 있다. 여기서는 Knight
의 주소를 가리키고 있게 되는 것이다.
this->hp = 0;
을 추가하여 비교해보자.
똑같이 동작하는 것을 알 수 있다. 메모리에 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;
}
}
브레이크 포인트를 걸어 확인해보면, 클래스가 생성될 때 생성자가 호출되고 메인 함수가 종료될 때 소멸자가 호출된다.
👻 글을 마치며
이번 시간에는 생성자와 소멸자에 대해 알아보았다. 아주 얕은 지식으로 알고 있었던 부분인데 개념 정리가 확실하게 된 것 같다. 생각보다 많은 기능을 가지고 있어 다방면으로 활용하기 좋을 것 같다.
Leave a comment