2022. 7. 1. 11:26ㆍ♣ C++
이번 포스팅에선 생성자와 소멸자에 대해 알아보겠습니다.
생성자: 클래스 객체를 만들 때 매개 변수를 초기화하는 함수
소멸자: 객체의 수명이 끝나면 정리해주는 멤버 함수
생성자는 클래스의 시작, 소멸자는 클래스의 끝이라고 보면 되겠다.
개발 환경 : VSCode, Windows 10
Contents
📌 생성자 정의 및 선언
📌 디폴트 생성자
📌 복사 생성자
📌 소멸자
기본 개발 세팅은 다음과 같습니다.
#include <iostream>
#include <string.h>
#include <string>
using namespace std;
int main()
{
return 0;
}
📌 생성자 정의 및 선언
앞서 배웠던 클래스는 거의 자신만의 매개 변수를 가졌습니다.
따라서 클래스 객체를 생성할 땐 매개 변수도 무조건 초기화해줘야 합니다.
생성자는 매개 변수 초기화만을 수행하는 public 함수이며, 객체를 선언하는 동시에 불러옵니다.
[생성자를 선언할 때의 규칙]
(1) 초기화를 위한 데이터만을 인수로 받습니다.
(2) 매개 변수를 초기화하는 함수이므로, 반환 값은 없습니다. 하지만 void 선언을 하지 않습니다.
(3) 초기화 방법이 여러개일 경우, 오버로딩 규칙에 의해 여러 개를 생성할 수 있습니다.
🎨 이제 직접 코드를 통해 생성자 선언과 사용 예시를 살펴보겠습니다.
먼저 class 선언입니다.
class player
{
private:
string name_;
int height_;
public:
player(const string& name, int height);
void Display();
};
운동선수 클래스를 만들었습니다. private 제어 지시자에 매개 변수를 선언하고, public엔 생성자를 선언했습니다. 앞서 말했던 대로, 반환 값이 없지만 void 선언을 하지 않은 것을 볼 수 있습니다. 매개 변수들을 보여줄 Display() 함수도 선언했습니다.
함수 정의를 해줍니다.
생성자는 void와 같은 선언이 없습니다.
player::player(const string& name, int height)
{
name_ = name;
height_ = height;
}
void player::Display()
{
cout << "선수의 이름 : " << name_ << endl;
cout << "선수의 키 : " << height_ << endl;
}
인수를 받아서 그대로 매개 변수 초기화를 진행합니다. Display() 함수는 일정 양식에 맞게 선수의 이름과 키를 보여주도록 합니다.
main() 코드
int main()
{
// 1
player nadal("나달", 183);
nadal.Display();
// 2
player federer = player("페더러", 184);
federer.Display();
return 0;
}
두 명의 선수 객체를 선언했습니다. 테니스계에서 유명한 나달과 페더러입니다. 그런데 이들 객체를 생성하는 방법이 조금 다릅니다. 페더러 객체는 명시적으로 호출을 이용하여 선언했습니다. 이 코드들의 결과는 어떨까요?
결과
선수의 이름 : 나달
선수의 키 : 183
선수의 이름 : 페더러
선수의 키 : 184
이쁘게 잘 나온 것을 확인했습니다. 생성자는 클래스 매개 변수를 초기화하는 함수임을 인지하시고, 이제 디폴트 생성자로 넘어가겠습니다.
📌 디폴트 생성자
디폴트 생성자는 매개 변수의 초깃값을 미리 정의해두는 것을 말합니다. 만약 사용자가 객체를 선언하며 초깃값을 명시하지 않으면 컴파일러에 의해 자동적으로 제공하도록 합니다. 단, 클래스에서 생성자가 단 하나도 정의되지 않았을 때만 자동으로 제공합니다. 생성자가 있다면 오류를 발생시킵니다.
초깃값을 명시하지 않고 객체를 생성하고 싶을 때 디폴트 생성자를 정의합니다.
[디폴트 생성자 정의]
(1) 디폴트 인수를 이용합니다.
=> 클래스에서 함수를 정의할 때 디폴트 인수를 미리 선언합니다.
// 클래스 선언
class player
{
private:
string name_;
int height_;
public:
player(const string& name = "나달", int height = 183);
void Display();
};
// 함수 정의
player::player(const string& name, int height)
{
name_ = name;
height_ = height;
}
void player::Display()
{
cout << "선수의 이름 : " << this->name_ << endl;
cout << "선수의 키 : " << this->height_ << endl;
}
player(const string& name = "나달", int height = 183); 처럼 함수 원형을 정의할 때부터 디폴트 인수를 깔아놓고 시작하면 이것이 디폴트 생성자가 됩니다.
(2) 함수 오버로딩을 이용합니다.
=> 함수 오버로딩 규칙을 응용하여 여러 개의 생성자를 만드는 것입니다. 그중 하나의 생성자를 디폴트 생성자처럼 이용하는 것이 핵심입니다.
// 클래스 선언
class player
{
private:
string name_;
int height_;
public:
player(const string& name, int height);
player();
void Display();
};
// 함수 정의
player::player(const string& name, int height)
{
name_ = name;
height_ = height;
}
player::player()
{
name_ = "쿠쿠롱루삥뽕";
height_ = 120;
}
하나는 name, height 매개 변수가 있으며, 다른 하나는 없습니다. C++은 이 둘을 오버로딩으로 인식하며, 사용자가 만약 디폴트 생성자를 사용하고 싶다면 매개 변수를 넣지 않고 객체를 정의하면 됩니다.
main() 함수입니다.
int main()
{
// 1
player nadal("나달", 183);
nadal.Display();
// 2
player federer = player();
federer.Display();
return 0;
}
결과
선수의 이름 : 나달
선수의 키 : 183
선수의 이름 : 쿠쿠롱루삥뽕
선수의 키 : 120
[디폴트 생성자를 가지는 객체의 선언]
=> class이름 객체(); 는 자칫하면 함수로 인식될 수 있습니다. 따라서 디폴트 생성자로 객체를 선언할 땐 다음 3가지의 형태를 따릅니다.
// (1)
player nadal;
// (2)
player nadal = player();
// (3)
player *ptr_nadal = new player;
다만, 마지막 세번째 방법을 이용하고 난 후에는 delete를 통해 꼭 메모리 할당 해제를 해줘야 합니다.
📌 복사 생성자
객체가 다른 객체를 복사하고 싶다면 어떻게 해야 할까요?
player nadal;
player federer = player("페더러", 184);
두 개의 객체가 생성되었습니다. 하나는 디폴트 생성자로 객체를 생성했고, 하나는 명시적으로 매개 변수를 정의했습니다.
그런데 nadal 객체에 federer 객체의 내용을 복사하고 싶습니다.
대입 연산자를 이용했습니다.
nadal = federer
이렇게 복사하는 것을 얕은 복사라고 합니다. 값을 복사하는 것이 아니라 값을 가리키는 포인터를 복사하는 것입니다. 객체 대입을 얕은 복사로 진행하면 문제가 생길 수 있습니다. 따라서 깊은 복사로 객체 대입을 진행해야 합니다.
깊은 복사란? 값 자체를 복사하는 것을 말합니다.
클래스에서는 이런 깊은 복사를 가능케 하는 복사 생성자를 정의할 수 있습니다.
복사 생성자는 자신과 같은 클래스 타입의 객체에 대한 참조를 인수로 받아서 자신을 초기화하는 생성자입니다.
다음은 복사 생성자를 정의한 클래스입니다.
// 복사 생성자는 클래스 객체를 인수로 받습니다.
player(const player&);
// 클래스 정의
class player
{
private:
string name_;
int height_;
public:
player(const string& name, int height);
player();
player(const player&);
void Display();
};
복사 생성자는 다음과 같은 방식으로 정의됩니다.
이를 보면 알 수 있듯이, 복사 생성자는 값을 복사하는 것에 초점을 맞추고 있습니다.
player::player(const player& another)
{
name_ = another.name_;
height_ = another.height_;
}
main() 함수입니다.
int main()
{
// 1
player federer = player("페더러", 184);
federer.Display();
// 깂 복사
player nadal(federer);
nadal.Display();
return 0;
}
페더러 객체를 생성한 후, 나달 객체를 생성할 땐 복사 생성자를 이용했습니다.
이에 따라 페더러 객체의 정보가 모두 나달 객체에도 복사되었습니다.
결과
선수의 이름 : 페더러
선수의 키 : 184
선수의 이름 : 페더러
선수의 키 : 184
📌 소멸자
객체의 수명이 끝나면 정리해주는 멤버 함수입니다.
클래스 이름과 같지만, 앞에 ~을 붙여줘서 구분합니다.
인수, 반환값이 없고, void 선언도 하지 않습니다. 단 하나만 정의합니다.
💥 주의 사항
(1) 만약 new 키워드를 통해 동적 할당을 해서 객체를 생성했다면, delete 키워드를 이용해서 정리해줘야 합니다.
(2) 소멸자는 알아서 작동합니다.
class player
{
private:
string name_;
int height_;
public:
player(const string& name, int height);
player();
player(const player&);
~player();
void Display();
};
~player(); 코드를 통해 소멸자를 정의해줍니다.
소멸자 정의
player::~player()
{
cout << this->name_ << "객체가 소멸되었습니다." << endl;
}
소멸자는 그저 player::~player(){} 만 해도 되지만, 소멸되었다는 것을 알리기 위해 약간의 코드를 첨가했습니다.
main() 함수입니다.
int main()
{
// 1
player federer = player("페더러", 184);
federer.Display();
// 값 복사
player nadal(federer);
nadal.Display();
return 0;
}
결과
선수의 이름 : 페더러
선수의 키 : 184
선수의 이름 : 페더러
선수의 키 : 184
페더러객체가 소멸되었습니다.
페더러객체가 소멸되었습니다.
지금까지 생성자와 소멸자에 대해 알아보았습니다.
클래스 객체를 생성함에 있어 편리한 도구가 될 것이니 잘 활용하시길 바랍니다.
수고하셨습니다.
'♣ C++' 카테고리의 다른 글
[C++] STL 개념 쉽게 이해하기 (0) | 2022.07.20 |
---|---|
[C++] 연산자를 커스텀해보자(operator overloading) (0) | 2022.07.03 |
[C++] 클래스(class) 개념 및 선언 (plus. this 포인터) (0) | 2022.06.30 |
[C++] 네임스페이스(namespace) 개념 및 코드 (0) | 2022.06.28 |
[C++] 자유 변수, 레지스터 변수, 정적 변수 ft. 유효 범위 (0) | 2022.06.27 |