본문 바로가기

Study/C++

[C++] Constructor & Destructor

생성자(Constructor)와 소멸자(Destructor)

  • 생성자는 다음과 같은 특징을 가지고 있다.
    • 클래스의 이름과 함수의 이름이 동일하다.
    • 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다.
    • 객체 생성시 딱 한번 호출된다.
    • 함수의 일종으로, 오버로딩이 가능하다.
    • 함수의 일종으로, 매개변수에 디폴트 값 설정이 가능하다.
  • 이러한 생성자를 이용하면, 객체 생성과 동시에 초기화가 가능하다.
#includes <iostream>
using namespace std;

class SimpleClass
{
private:
	int num1;
	int num2;
public:
	SimpleClass()
	{
		num1 = 0;
		num2 = 0;
	}
		SimpleClass(int n)
	{
		num1 = n;
		num2 = 0;
	}
	SimpleClass(int n1, int n2)
	{
		num1 = n1;
		num2 = n2;
	}
	/* Compile Error - ambiguous
	SimpleClass(int n1 = 0, int n2 = 0)
	{
		num1 = n1;
		num2 = n2;
	}
	*/
	void ShowData() const
	{
		cout << num1 << ' ' << num2 << endl;
	}
};

int main(void)
{
	SimpleClass sc1;
	sc1.ShowData();
	
	SimpleClass sc2(100);
	sc2.ShowData();
	
	SimpleClass sc3(100, 200);
	sc3.ShowData();
}
  • 만일 위 코드에서, 주석 부분이 나온다면 컴파일러는 ambiguous error를 발생시킬 것이다. 이는, 만일 SimpleClass sc2(100)과 같이 호출하였을때,  SimpleClass(int n)과 해당 에러 코드 모두 호출이 가능하기 때문에 발생하는 에러다.
  • 위 코드에서 할당 및 호출을 위해서는 다음과 같다.
    • SimpleClass sc2(100);                                   // 전역 및 지역변수 형태
    • SimpleClass * ptr2 = new SImpleClass(100) // 동적 할당 형태
  • 만일, 오버로딩된 함수를 호출하고 싶다면 다음과 같이 하면 될 것이다.
    • SimpleClass sc3(100, 200);
    • SimpleClass * ptr3 = new SimpleClass(100, 200);
  • 단, 다음과 같은 호출을 허용되지 않는다.
    • SimpleClass sc1();
  • 이는, 컴파일러가 이러한 유형의 문장을 객체를 통해 생성된 코드인지 함수 원형을 호출한 것인지 알 수 없기 때문이다. 따라서, C++에서는 통상 이러한 유형의 문장은 객체생성이 아닌, 함수의 원형에서만 사용하기로 하였다. 즉, 다음과 같은 선언은 가능하다.
    • SimpleClass sc1;
    • SimpleClass * ptr1 = new SimpleClass;
    • SimpleClass * ptr1 = new SimpleClass();

  • 생성자가 추가된 클래스의 선언은 다음과 같이 '멤버 이니셜라이저'를 이용하여 멤버를 초기화 하기도 한다.
class Rectangle
{
private:
	Point upLeft;
	Point lowRight;
public:
	Rectangle(const int &x1, const int &y1, const int &x2, const int &y2);
	void ShowRecInfo() const;
};

Rectangleconst int &x1, const int &y1, const int &x2, const int &y2) : upLeft(x1, y1), lowRight(x2, y2)
{
	...
}
  • 이 함수에서 나온 upLeft(x1, y1), lowRight(x2, y2)가 멤버 이니셜라이저로, 객체의 생성자 호출에 활용된다.
  • 앞선 과정들을 돌이켜, 객체의 생성 과정은 다음과 같이 정리된다.
    1. 메모리 공간 할당
    2. 이니셜라이저를 통한 멤버변수(객체) 초기화
    3. 생성자의 몸체부분 실행
  • 생성자의 경우, 따로 정의하지 않으면 디폴트 생성자(Default Constructor)가 자동으로 삽입되어 호출된다.
  • 일반적으로 멤버변수의 초기화에 있어서는 이니셜라이즈가 선호되는데, 이는 초기화의 대상을 명확히 인식할 수 있으며, 성능에 약간 이점이 있기 때문이다. 이는, 이니셜라이저를 이용하면 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성되기 때문이다. 이러한 특징 때문에, const 멤버변수의 경우에도 이니셜라이즈를 이용하면 초기화가 가능하다.
#include <iostream>
using namespace std;

class FruitSeller
{
private:
	const int APPLE_PRICE;
	int numOfApples;
	int myMoney;
public:
	FruitSeller(int price, int num, int money)
	: APPLE_PRICE(price), numOfApples(num), myMoney(money)
	{
	}
	int SalesApples(int money)
	{
		...
	}
	void ShowSalesResult() const
	{
		...
	}
};
  • 이니셜라이저의 이러한 특징은 멤버변수로 참조자를 선언할 수 있게 한다. const 변수와 마찬가지로, '참조자'도 선언과 동시에 초기화가 이루어져야한다. 따라서 이니셜라이저를 이용하면 참조자도 멤버변수로 선언될 수 있다.
class AAA
{
public:
	AAA()
	{
		cout << "empty object" << endl;
	}
	void ShowYourName()
	{
		cout << "I'm class AAA" << endl;
	}
};

class BBB
{
private:
	AAA &ref;
	const int &num;

public:
	BBB(AAA &r, const int &n): ref(r), num(n)
	{
	}
	void ShowYourName()
	{
		ref.ShowYourName();
		cout << "and" << endl;
		cout << "I ref num " << num << endl;
	}
};

  • 객체가 되기 위해서는  반드시 하나의 생성자가 호출되어야 한다. 따라서 별도의 생성자가 선언되지 않은 클래스의 경우 디폴트 생성자라는 것이 자동으로 삽입된다.
  • 즉, 모든 객체는 한번의 생성자 호출을 동반한다. 이는 new 연산자를 이용한 객체의 생성에도 해당하는 이야기인데, malloc의 경우 생성자가 호출되지 않는다.
  • 만일, 다음과 같이 정의된 클래스가 있다고 해보자.
class SoSimple
{
private:
	int num;
public:
	SoSimple(int n) : num(n) { }
}
  • 이와 같이 생성된 클래스는 디폴트 생성자가 삽입되지 않는다.
  • 따라서 다음과 같은 형태로 객체 생성은 가능하지만,
SoSimmple simObj1(10);
SoSimple * simPtr1 = new SoSimple(2);
  • 다음의 객체생성은 해당하는 생성자가 정의되지도, 자동으로 삽입되지도 않았기 때문에 불가능하다.
SoSimple simObj2;
SoSimple * simPtr2 = new SoSimple;

  • 객체의 생성이 클래스의 외부에서 진행되기 때문에, 생성자는 public으로 선언되어야 한다. 만일, 클래스 내부에서 객체를 생성한다면 생성자가 private로 선언되어도 되는것이다. 이러한 방법은 객체의 생성방법을 제한하고자 하는 경우에는 매우 유용하게 사용된다.
#include <iostream>
using namespace std;

class AAA
{
private:
	int num;
public:
	AAA() : num(0) {}
	AAA& CreateInitObj(int n) const
	{
		AAA * ptr = new AAA(n);
		return *ptr;
	}
	void ShowNum() const { cout << num << endl;}
private:
	AAA(int n) : num(n) {}
};

int main(void)
{
	AAA base;
	base.ShowNum();
	
	AAA &obj1 = base.CreateInitObj(3);
	obj1.ShowNum();
	
	AAA &obj2 = base.CreateInitObj(3);
	obj2.ShowNum();
	
	delete &obj1;
	delete &obj2;
	return 0;
}
  • 위 예제에서는 단순히, private로 선언된 생성자를 통해서도 객체의 생성이 가능함이 보여진다.

  • 객체생성시 반드시 호출되는 것이 생성자라면, 객체소멸시 반드시 호출되는 것이 소멸자이다. 다음은 소멸자의 특징이다.
    • 클래스 이름 앞에 '~'가 붙은 형태의 이름을 가진다.
    • 반환형이 선언되어 있지 않으며, 실제로 반환되지 않는다.
    • 매개변수는 void형으로 선언되어야 하기 때문에 오버로딩도, 디폴트 값 설정도 불가능하다.
  • 이러한 소멸자는 생성자에서 할당한 리소스의 소멸에 사용된다. 가령, 생성자 내에서 new 를 사용하여 힙에 할당해 놓은 메모리 공간이 있다면, 소멸자에서는 delete 연산자를 이용해서 이 메모리 공간을 소멸한다.
#include <iostream>
#include <cstring>
using namespace std;

class Person
{
private:
	char *name;
	int age;
public:
	Person(char *myname, int myage)
	{
		int len = strlen(myname)+1;	// Dynamic Allocation;
		name = new char[len];
		strcpy(name, myname);
		age = myage;
	}
	void ShowPersonInfo() const
	{
		cout << "이름: " << name << endl;
		cout << "나이: " << age << endl;
	}
	~Person()
	{
		delete []name;
		cout << "called destructor!" << endl;
	}
};

int main(void)
{
	Person man1("Lee dong woo", 29);
	Person man2("Jang dong gun", 41);
	man1.ShowPersonInfo();
	man2.ShowPersonInfo();
	return 0;
}

 

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

[C++] Init  (0) 2023.10.06
[C++] Class, Array and this pointer  (0) 2023.10.06
[C++] Information Hiding  (0) 2023.10.06
[C++] OOP; Object-Oriented Programming  (0) 2023.10.06
[C++] Structure  (0) 2023.10.06