본문 바로가기

Programming Languages/C++

[클래스와 객체]-생성자와 소멸자-소멸자

소멸자(destructor)는 객체가 소멸될 때 자동으로 실행되는 함수로서, 객체의 소멸에 따라 필요한 제반 처리를 하기 위한 코드가 포함된다. 클래스 선언문 내에서 소멸자의 선언 형식은 다음과 같다.

class ClassName{
      ......
public:
      ......
      ~ClassName(){   // 소멸자
            ......
      }
};

소멸자의 선언형식 역시 일반 멤버함수와 유사하나, 다음과 같이 고유한 사항도 있다.

  1. 소멸자는 클래스이 이름에 ' ~ '를 붙여 선언한다.
  2. return 명령으로 값을 반환할 수 없으며, 함수 머리에 반환할 자료형을 표시하지 않는다.
  3. 매개변수를 포함할 수 없다.
  4. 소멸자는 다중정의할 수 없으며, 클래스에 하나만 정의한다.
  5. public 멤버로 선언하는 것이 일반적이다.
  6. 상속을 통해 파생 클래스를 정의하는 경우 virtual을 지정하여 가상함수가 되도록 하는 것이 좋다.

[예제 4-2]에서는 생성자 및 소멸자를 선언하고 활용하는 사례를 보여 준다. 또한 객체에 대한 포인터와 this 포인터 사용에 대하여 소개한다.

사람을 나타내는 클래스를 선언하고자 한다. 사람 객체는 '***에 사는 ***입니다'라고 자신을 알릴 수 있으며,
주소를 변경할 수 있다

Person이 할 수 있는 행위와 이러한 동작을 하기 위해 표현해야 할 객체 내부의 상태는 각각 다음과 같다.

|표 4-4| Person 클래스의 메소드

메소드 비고
Person(char *name, char *addr) 생성자
~Person() 소멸자
void print() '***에 사는 ***입니다'라고 출력한다.
void chAddr(char *newAddr) 주소를 변경한다.

|표 4-5| Person 클래스의 속성

속성 비고
char *name 이름을 저장한다.
char *addr 주소를 저장한다.

[그림 4-4]는 위 표의 내용들로 만들어진 클래스 Person의 클래스 다이어그램이다

Person
- name : char*
- addr : char*
+ Person(name : char*, addr : char*)
+ ~Person()
+ print() : void
+ chAddr(newAddr : char*) : void
// 소스코드 4-8 : Person.h
#ifndef PERSON_H_INCLUDED
#define PERSON_H_INCLUDED

class Person { // 클래스 Person의 선언 시작
	char* name; // 이름을 저장하는 데이터 멤버
	char* addr; // 주소를 저장하는 데이터 멤버
public: // public 멤버함수
	Person(const char* name, const char* addr); // 생성자
	~Person();
	void print() const; // 이름과 주소 출력
	void chAddr(const char* newAddr); // 주소 변경
};

#endif // !PERSON_H_INCLUDED

[소스코드 4-8] Person.h의 클래스 선언문에서는 멤버함수들의 원형만 선언하고 있다. 이 경우 실제 멤버함수를 별도의 소스 프로그램 파일에서 정의한다. [소스코드 4-9] Person.cpp는 Person 클래스의 멤버함수들을 정의한다.

// 소스코드 4-9 : Person.cpp
#include <iostream>
#include <cstring>
#include "Person.h"
#pragma warning(disable : 4996)warning pragma
using namespace std;

Person::Person(const char* name, const char* addr) {
	// 이름을 저장할 공간 할당
	this->name = new char[strlen(name) + 1];
	// 데이터 멤버 name에 이름을 복사
	strcpy(this->name,name);
	// 주소를 저장할 공간 할당
	this->addr = new char[strlen(addr) + 1];
	// 데이터 멤버 addr에 주소를 복사
	strcpy(this->addr,addr);
    	cout << "Person 객체 제거함(" << name << ")" << endl;


}

Person::~Person() { // 소멸자
	cout << "Person 객체 제거함(" << name << ")" << endl;
	delete[] name; // 이름 저장공간 반납
	delete[] addr; // 주소 저장공간 반납
}

void Person::print() const {
	cout << addr << "에 사는 " << name << "입니다." << endl;
}

void Person::chAddr(const char* newAddr) {
	delete[] addr; // 기존 공간 반납
	// 새로운 주소에 맞는 공간 할당
	addr = new char[strlen(newAddr) + 1];
	strcpy(addr,newAddr); // 데이버 멤버 addr에 새로운 주소를 복사
}

클래스의 멤버함수를 정의하기 위해서는 클래스에 대한 정보가 필요하므로, Person.cpp에 Person.h를 삽입함으로써 Person 클래스가 선언되도록 하고 있다. 이후 Person 클래스의 멤버함수들이 정의되는데, 이제는 Person 클래스 선언문의 외부에 위치하므로 각각의 함수들이 Person 클래스의 멤버임을 알려야 한다. 이를 위해 멤버함수 이름 앞에 'Person::'을 기입하고 있다. 이와 같이 함수의 소속을 알리고 나면 함수 몸체 내에서는 데이터 멤버나 다른 멤버함수를 이름만으로 사용할 수 있다.

정의된 생성자에서는 주소와 이름을 저장할 수 있는 공간을 할당하여 인수로 지정된 주소 및 이름을 복사하는 초기화 작업을 수행한다. 이때 형식 매개변수인 nameaddr가 객체의 데이터 멤버 이름과 같은 것을 볼 수 있다.  이러한 경우 가장 최근에 선언되는 것이 그 이름을 사용하게 되며, 함수 내부에서 name이나 addr을 사용할 경우 형식 매개변수인 name이나 addr을 의미하게 된다. 만일 이 이름들이 데이터 멤버를 지칭하게 하고 싶다면 구체적으로 소속을 알려야 하는데, 한 가지 방법은 this라는 포인터를 사용하는 것이다.

this는 객체 자기 자신을 가리키는 포인터이다. 그러므로 this->name이라고 표기하면 객체의 데이터 멤버 name을 지칭하는 것이다.

소멸자에서는 할당된 공간을 반납하는 객체의 정리작업을 한다. 생성자와 소멸자에는 각각 객체의 생성 및 소멸을 알리는 메시지를 콘솔을 통해 출력하게 했는데, 이것은 단지 생성자 및 소멸자의 동작을 이해할 수 있도록 화면을 통해 볼 수 있게 하기 위한 것으로, 실제 응용에서는 이와 같은 불필요한 문장은 사용하지 않는다. 생성자와 소멸자에서는 최소한의 초기화 및 정리작업만 이루어지도록 해야 한다.

// 소스코드 4-10 : PrsnMain.cpp
#include <iostream>
#include "Person.h"
using namespace std;

int main() {
	Person* p1 = new Person("이철수", "서울시 종로구");
	Person* p2 = new Person("박은미", "강원도 동해시");
	p1->print();
	p2->print();
	cout << endl << "주소 변경:";
	p2->chAddr("대전시 서구");
	p2->print();
	delete p1;
	delete p2;
	return 0;

}

====== 결과 ======
Person 객체 생성함(이철수)
Person 객체 생성함(박은미)
서울시 종로구에 사는 이철수입니다.
강원도 동해시에 사는 박은미입니다.

주소 변경:대전시 서구에 사는 박은미입니다.
Person 객체 제거함(이철수)
Person 객체 제거함(박은미)

[소스코드 4-10] PrsnMain.cpp에서는 Person 객체를 new 연산자를 통해 동적 할당하고 있다. 이때 생성자가 동작하면서 화면에 출력되는 메시지를 통해 Person 객체가 생성됨을 알 수 있다. 마찬가지로 delete 연산자를 통해 객체를 반납할 때 소멸자가 동작하는 것을 볼 수 있다. 구조체에서와 마찬가지로 포인터를 이용하여 객체의 멤버를 액세스할 때에는 -> 연산자를 사용한다.