C++(cpp)
[cpp] 소멸자, 복사 생성자
Jueun Park
2021. 6. 24. 12:38
소멸자
생성자의 반대격, 객체가 사라질 때 호출되는 함수(자동으로)이다. 객체 내에 new로 동적 생성한 변수 등이 있을때 유용함.
- 생성자와의 공통점: 디폴트 생성자가 있음, 그러나 아무 행동도 하지 않음.
- 생성자와의 차이점: 오버로딩 불가능
- 형식: ~(클래스의 이름)
- 사용예시
#include <iostream>
class Example{
private:
char* _name;
public:
// 생성자
Example(){
_name = NULL;
}
Example(char const* name){
_name = new char[strlen(name) + 1];
strcpy(_name, name);
}
// 소멸자
~Example(){
if (_name) {
std::cout << _name << " : name deleted\n" << std::endl;
delete[] _name;
}
else {
std::cout << "noname" << " : has noname\n" << std::endl;
}
}
};
int
main()
{
Example noname;
Example cadet("cadet");
Example ensign("ensign");
Example commander("commander");
return (0);
}
결과창:
commander : name deleted
ensign : name deleted
cadet : name deleted
noname : has noname
- 인스턴스가 지역인지 전역인지에 따른 소멸 과정 살펴보기.
- 그런데 인스턴스가 뭔가요?
- 생성 순서와 반대로 소멸 되는 이유는 스택(stack)의 후입선출(LIFO, Last-In First-Out) 특성 때문이다.
복사 생성자
해당 클래스의 객체를 인자로 넣으면, 그대로 복사해주는 생성자. 같은 상수를 가진 객체를 만들기 쉽다.
[ 예제1 ] cpp에서 제공하는 복사 생성자를 이용한 경우(디폴트 복사 생성자)
#include <iostream>
class Example {
private:
int _x;
int _y;
public:
Example(int x, int y);
void print();
};
Example::Example(int x, int y) : _x(x), _y(y) {
std::cout << "생성자 호출" << std::endl;
}
void Example::print() {
std::cout << "_x: " << _x << std::endl;
std::cout << "_y: " << _y << std::endl;
}
int
main()
{
Example example(1, 3);
example.print();
Example example2(example);
example2.print();
Example example3 = example2;
example2.print();
}
결과창:
생성자 호출
_x: 1
_y: 3
_x: 1
_y: 3
_x: 1
_y: 3
[ 예제2 ] 내가 만든 복사 생성자를 이용
#include <iostream>
class Example {
private:
int _x;
int _y;
public:
Example(int x, int y);
Example(Example const& copy);
void print();
};
Example::Example(int x, int y) : _x(x), _y(y) {
std::cout << "생성자 호출" << std::endl;
}
Example::Example(Example const& copy){
std::cout << "복사생성자 호출" << std::endl;
_x = copy._x;
_y = copy._y;
}
void Example::print() {
std::cout << "_x: " << _x << std::endl;
std::cout << "_y: " << _y << std::endl;
}
int
main()
{
Example example(1, 3);
example.print();
Example example2(example);
example2.print();
Example example3 = example2;
example2.print();
}
결과창:
생성자 호출
_x: 1
_y: 3
복사생성자 호출
_x: 1
_y: 3
복사생성자 호출
_x: 1
_y: 3
복사 생성자의 한계
디폴트 복사 생성자를 이용하여 복사 생성 해주는 아래와 같은 코드를 짰다.
#include <iostream>
#include <string.h>
class Example {
private:
int _x;
int _y;
char* _name;
public:
Example(int x, int y, char const* name);
void print();
};
Example::Example(int x, int y, char const* name) : _x(x), _y(y) {
_name = new char[strlen(name + 1)];
strcpy(_name, name);
std::cout << "생성자 호출" << std::endl;
}
void Example::print() {
std::cout << "name: " << _name << std::endl;
std::cout << "name-address: " << &_name << std::endl;
std::cout << "_x: " << _x << std::endl;
std::cout << "_y: " << _y << std::endl;
}
int
main()
{
Example example(1, 3, "example");
example.print();
Example example2(example);
example.print();
Example example3(1, 3, "example");
example2.print();
Example example4(example3);
example2.print();
}
각 객체의 name의 주소를 뽑아보면
결과창:
생성자 호출
name: example
name-address: 0x7ffee52a0808
_x: 1
_y: 3
name: example
name-address: 0x7ffee52a0808
_x: 1
_y: 3
생성자 호출
name: example
name-address: 0x7ffee52a07e8
_x: 1
_y: 3
name: example
name-address: 0x7ffee52a07e8
_x: 1
_y: 3
첫번째 객체는 생성된 것이고, 두번째 객체는 복사된 객체
세번째 객체는 생성된 것이고, 네번재 객체는 복사된 객체
첫번째와 두번째 객체의 _name 멤버변수는 같은 주소를 가리키고 있고
세번째와 네번째 객체의 _name 멤버변수는 같은 주소를 가리키고 있다.
만약에 아래처럼 소멸자에 _name을 delete해주는 코드를 추가한다면
#include <iostream>
#include <string.h>
class Example {
private:
int _x;
int _y;
char* _name;
public:
Example(int x, int y, char const* name);
void print();
~Example();
};
Example::Example(int x, int y, char const* name) : _x(x), _y(y) {
_name = new char[strlen(name + 1)];
strcpy(_name, name);
std::cout << "생성자 호출" << std::endl;
}
Example::~Example(){
delete[] _name;
}
void Example::print() {
std::cout << "name: " << _name << std::endl;
std::cout << "name-address: " << &_name << std::endl;
std::cout << "_x: " << _x << std::endl;
std::cout << "_y: " << _y << std::endl;
}
int
main()
{
Example example(1, 3, "example");
example.print();
Example example2(example);
example.print();
Example example3(1, 3, "example");
example2.print();
Example example4(example3);
example2.print();
}
생성자 호출
name: example
name-address: 0x7ffee8ab8808
_x: 1
_y: 3
name: example
name-address: 0x7ffee8ab8808
_x: 1
_y: 3
생성자 호출
name: example
name-address: 0x7ffee8ab87e8
_x: 1
_y: 3
name: example
name-address: 0x7ffee8ab87e8
_x: 1
_y: 3
소멸자 호출
소멸자 호출
a.out(82457,0x109f95dc0) malloc: *** error for object 0x7fd5ef4058b0: pointer being freed was not allocated
a.out(82457,0x109f95dc0) malloc: *** set a breakpoint in malloc_error_break to debug
[1] 82457 abort ./a.out
위와 같은 에러가 난다.
(네번째 객체의 멤버변수인 _name을 free하고, 세번째 객체의 멤버변수인 _name을 free하려고 할때에 에러가 나게되기 때문에 소멸자 호출이라는 메세지가 두번만 표시됨)
디폴트 복사 생성자를 사용하게 될 경우를 얕은 복사라고 부름. char 배열 등 깊은 복사(deep copy)가 필요한 것들은 디폴트 복사 생성자를 이용하지 말고 따로 동적 할당까지 해서 복사해주는 함수를 만들어서 사용해야함.
deep copy 예제
#include <iostream>
#include <string.h>
class Example {
private:
int _x;
int _y;
char* _name;
public:
Example(int x, int y, char const* name);
Example(Example const& copy);
void print();
~Example();
};
Example::Example(int x, int y, char const* name) : _x(x), _y(y) {
_name = new char[strlen(name + 1)];
strcpy(_name, name);
std::cout << "생성자 호출" << std::endl;
}
Example::Example(Example const& copy){
std::cout << "(Deep copy) 복사생성자 호출" << std::endl;
_name = new char[strlen(copy._name + 1)];
strcpy(_name, copy._name);
_x = copy._x;
_y = copy._y;
}
Example::~Example(){
std::cout << "소멸자 호출" << std::endl;
delete[] _name;
}
void Example::print() {
std::cout << "name: " << _name << std::endl;
std::cout << "name-address: " << &_name << std::endl;
std::cout << "_x: " << _x << std::endl;
std::cout << "_y: " << _y << std::endl;
}
int
main()
{
Example example(1, 3, "example");
example.print();
Example example2(example);
example.print();
Example example3(1, 3, "example");
example2.print();
Example example4(example3);
example2.print();
}