'생성자'와 '소멸자'는 클래스 객체가 생성 및 소멸될 때 '자동으로' 호출되는 함수이다. 이 두 함수의 특징은 함수임에도 '반환 형식이 없다'는 것과 함수 이름이 클래스 이름과 같다. 다만 소멸자의 이름 앞에는 ~가 붙는다.
클래스이름();
~클래스이름();
매개변수가 하나도 없는 생성자를 '디폴트 생성자' 라고 한다. 클래스 제작자가 디폴트 생성자와 소멸자를 기술하지 않아도 컴파일러가 알아서 만들어 넣는다는 점 즉, '생성자와 소멸자가 없는 클래스는 없다'를 기억하자
#include <iostream>
using namespace std;
class RAIN
{
public :
RAIN() // 생성자
{
cout << "RAIN::RAIN()" << endl;
}
~RAIN() // 소멸자
{
cout << "RAIN::~RAIN()" << endl;
}
};
int main(void)
{
cout << "시작" << endl;
RAIN r;
cout << "끝" << endl;
return 0;
}
실행결과를 보기전에 어떻게 출력될지 예상해보자
실행결과
아마 실행 결과를 잘못 예측했다면 대부분 '끝'이 출력되기 전에 소멸자가 호출될 것으로 예측했을 가능성이 높다.
중요한 점은 21번행에서 인스턴스 r을 선언했고 이 객체가 생성되는 시점이라는 것과 r이 main() 함수에 속한 지역 변수라는 점이다. 지역 변수는 특성상 선언된 블록 범위가 끝나면 자동으로 소멸한다. 따라서 main() 함수의 실행이 끝나면 r은 소멸한다.
// 위의 코드를 이와같이 수정하면 결과는 어떻게 될지 생각해보자
17 RAIN r;
18 int main(void)
19 {
20 cout << "시작" << endl;
21 cout << "끝" << endl;
22
23 return 0;
24 }
실행결과
이와같이 흥미로운 결과가 나왔다. "C로 작성한 프로그램에서 가장 먼저 호출되는 함수는 main() 함수다"라는 명제가 깨졌기 때문이다. C++ 에서는 전역 변수로 선언한 클래스의 생성자가 main() 함수보다 먼저 호출된다.
위의 내용을 숙지하고 풀어보자
#include <iostream>
using namespace std;
class RAIN
{
public :
RAIN() // 생성자
{
cout << "RAIN::RAIN()" << endl;
}
~RAIN() // 소멸자
{
cout << "RAIN::~RAIN()" << endl;
}
};
int main(void)
{
cout << "시작" << endl;
RAIN r;
cout << "중간" << endl;
RAIN rr;
cout << "끝" << endl;
return 0;
}
실행결과
이처럼 main() 함수의 마지막 구문이 실행된 후 내부에 선언된 지역 변수들이 소멸된 것을 확인할 수 있다.
정리하자면
- main ( ) 함수가 호출되기 전에 생성자가 호출될 수 있다.
- 생성자는 다중 정의할 수 있다.
- 소멸자는 다중 정의할 수 없다.
- main ( ) 함수가 끝난 후에 소멸자가 호출될 수 있다.
- 생성자와 소멸자는 생략할 수 있으나 생략할 경우 컴파일러가 만들어 넣는다.
새로운 생성자를 만들면서 디폴트 생성자를 생략할 수 있다. 앞서 말한 내용으로 '디폴트 생성자'는 매개변수가 없는 생성자이다.
#include <iostream>
using namespace std;
class RAIN
{
int Data;
public :
// 생성자의 매개변수로 전달된 값으로 멤버 변수를 초기화한다.
RAIN(int Param) : Data(Param) // 생성자
{
cout << "RAIN::RAIN()" << endl;
}
~RAIN() // 소멸자
{
// 생성할 때 매개변수로 받은 값을 출력한다.
cout << "RAIN::~RAIN()" << Data << endl;
}
};
int main(void)
{
cout << "시작" << endl;
RAIN r(1); // 생성자 호출
cout << "중간" << endl;
RAIN rr(2); // 생성자 호출
cout << "끝" << endl;
return 0;
}
실행결과
생성자 다중 정의
생성자가 다중 정의될 경우 사용자는 편하겠지만 제작자는 동일한 멤버를 초기화하는 코드를 여러번 기술해야 하는 번거러움이 있었다. 하지만 C++11 표준부터 '생성자 위임'이 지원되어 이러한 번거러움을 해소하게 되었다.
#include <iostream>
using namespace std;
class RAIN
{
public :
RAIN(int x)
{
cout << "RAIN(int)" << endl;
// x 값이 100 이 넘는지 검사하고 넘으면 100으로 맞춘다.
if(x > 100)
x = 100;
r_x = 100;
}
RAIN(int x, int y)
// x 값을 검사하는 코드는 이미 존재하므로 재사용한다. (7번행)호출
: RAIN(x)
{
cout << "RAIN(int,int)" << endl;
// y 값이 200이 넘는지 검사하고 넘으면 200으로 맞춘다.
if(y > 200)
y = 200;
r_y = 200;
}
void Print()
{
cout << "x:" << r_x << endl;
cout << "y:" << r_y << endl;
}
private :
int r_x = 0;
int r_y = 0;
};
int main(void)
{
// 매개변수가 하나인 생성자만 호출한다.
RAIN r(110);
r.Print();
// 이번에는 두 생성자 모두 호출한다.
RAIN rr(50, 250);
rr.Print();
return 0;
}
실행결과
19번행에서 RAIN(int,int) 생성자는 초기화 목록에서 RAIN(int) 생성자가 추가로 호출될 수 있도록 (재사용할 수 있도록) 위임했다.
동적 객체의 생성과 소멸
new와 delete 연산자는 각각 생성자와 소멸자를 호출한다.
#include <iostream>
using namespace std;
class RAIN
{
int Data;
public :
RAIN()
{
cout << "RAIN::RAIN()" << endl;
}
~RAIN()
{
cout << "RAIN::~RAIN()" << endl;
}
};
int main(void)
{
cout << "시작" << endl;
// new 연산자를 이용해 동적으로 객체를 생성한다.
RAIN *pData = new RAIN;
cout << "RAIN" << endl;
// delete 연산자를 이용해 객체를 삭제한다.
delete pData;
cout << "끝" << endl;
return 0;
}
실행결과
객체를 동적으로 생성할 때 한 가지 주의할 점은 '배열로 생성한 객체들은 반드시 배열로 삭제'해야 한다.
20 int main(void)
21 {
22 cout << "시작" << endl;
23
24 // 배열로 new 연산을 수행할 수 있다.
25 RAIN *pData = new RAIN[3];
26
27 // 배열로 생성한 것은 배열로 삭제한다!
28 delete [ ] pData;
29 cout << "끝" << endl;
30
31 return 0;
32 }
이와 같이 수정하고 결과를 확인해보자
실행결과
객체를 배열로 생성했으니 배열로 삭제하는 것이 당연하다. 만약 배열로 삭제하지 않을 경우
28 delete pData;
실행결과
한 가지 다행스러운 것은 클래스가 아니라 char나 int 같은 기본 자료형의 배열은 배열로 삭제하지 않는다고 하더라도 오류는 발생하지 않는다.
'Develop > C++' 카테고리의 다른 글
[C ++] 복사 생성자 (0) | 2023.02.04 |
---|---|
[C ++] 상수형 메소드 (0) | 2023.02.03 |
[C ++] 접근 제어 지시자 (0) | 2023.02.03 |
[C ++] 클래스 기본 문법 (0) | 2023.02.03 |
[C ++] Namespace 와 using (0) | 2023.02.02 |
댓글