생성자(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)가 멤버 이니셜라이저로, 객체의 생성자 호출에 활용된다.
- 앞선 과정들을 돌이켜, 객체의 생성 과정은 다음과 같이 정리된다.
- 메모리 공간 할당
- 이니셜라이저를 통한 멤버변수(객체) 초기화
- 생성자의 몸체부분 실행
- 생성자의 경우, 따로 정의하지 않으면 디폴트 생성자(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 #
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 |