프로그램에서 마무리 작업
컴퓨터에서는 하나 이상의 프로그램들이 동시에 실행되고 있다. 컴퓨터가 제공하는 여러 자원을 사용하다가 제대로 정리하지 않고 프로그램을 종료하게 되면 해당 자원을 다른 프로그램이 사용하지 못할 수도 있다. 이와 같은 맥락으로 프로그램은 객체들에게 자원을 제공한다고 생각하면 된다. 따라서 생성 된 객체들은 사용이 끝날 때에 같은 프로그램 내에서 실행 중이거나 새롭게 생성될 객체를 위해서 정리를 해주어야 한다. 간단한 예를 들어보겠다.
#include <stdio.h>
#include <string.h>
#include <malloc.h>
class MyString
{
private:
char *mp_string;
public:
MyString()
{
mp_string = NULL;
}
void SetString(char *p_string)
{
if(NULL != mp_string) free(mp_string);
int len = strlen(p_string);
mp_string = (char *)malloc(len + 1);
strcpy(mp_string, p_string);
}
const char *GetString()
{
return mp_string;
}
void DeleteString()
{
if(NULL != mp_string){
free(mp_string);
mp_string = NULL;
}
}
};
int main()
{
MyString str;
str.SetString((char*)"Destructor");
printf("%s\n", str.GetString());
str.DeleteString();
return 0;
}
위의 소스코드에 있는 클래스는 문자열을 입력받아 출력시키는 클래스이다. 'main' 함수에서 만들어진 str 객체에 문자열을 저장하기 위해서 'SetString' 함수를 사용한다. 함수는 넘겨받은 문자열의 길이 + 1만큼의 크기의 힙영역의 메모리 주소를 멤버 변수에 저장한다. 그 곳에 넘겨받은 문자열을 저장하는 것이다. 즉, 객체에서 동적할당을 받아서 사용하는 것이기 때문에 객체의 사용을 종료할 때는 동적할당을 해제해야 한다. 그래서 'main'의 마지막 부분에서 객체의 'DeleteString' 함수를 사용하여 동적할당을 해제해주었다. 이렇듯 위의 예시에서는 사용자가 직접 동적할당을 해제하기 위해서 별도의 함수를 따로 호출해주어야 한다. 할당해제를 하지 않고 객체가 제거되면 할당된 메모리는 프로그램에 그대로 남아서 다른 객체들이 사용할 수 없게 된다. 객체 생성자를 생각해보자 사용자가 실수로 멤버 변수의 초기화를 해주지 않았을 경우에 원치 않는 결과가 나올 수 있다고 하였다. 그렇기 대문에 객체 생성자를 사용한다고 했다. 이와 마찬가지로 객체 파괴자도 사용자가 실수를 객체의 마무리 작업을 실행하지 않았을 때를 대비한 기술이라고 생각하면 된다.
객체 파괴자
C++ 클래스는 객체가 파괴될 때 자동으로 함수를 호출하는 객체 파괴자를 제공한다. 즉, 위의 예시에서의 객체의 마무리 작업인 'DeleteString'을 클래스에서 자동으로 호출해줄 수 있게 된 것이다. 객체 파괴자의 이름은 객체 생성자와 마찬가지로 함수의 이름이 클래스의 이름과 동일하다. 하지만, 다른 점이 하나 있는데 이름 앞에 '~'을 사용해주어야 한다. 위의 클래스 이름이 'MyString'이므로 객체 생성자의 이름도 'MyString'이다. 객체 파괴자는 '~MyString'으로 사용해주어야 한다. 아래가 객체 파괴자를 사용하여 수정한 소스코드이다.
#include <stdio.h>
#include <string.h>
#include <malloc.h>
class MyString
{
private:
char *mp_string;
public:
MyString()
{
mp_string = NULL;
}
~MyString()
{
DeleteString();
}
void SetString(char *p_string)
{
if(NULL != mp_string) free(mp_string);
int len = strlen(p_string);
mp_string = (char *)malloc(len + 1);
strcpy(mp_string, p_string);
}
const char *GetString()
{
return mp_string;
}
void DeleteString()
{
if(NULL != mp_string){
free(mp_string);
mp_string = NULL;
}
}
};
int main()
{
MyString str;
str.SetString((char*)"Destructor");
printf("%s\n", str.GetString());
return 0;
}
클래스의 멤버 함수로 객체 파괴자가 추가되었다. 'main' 함수에서는 따로 호출해주었던 'DeleteString' 함수 호출 구문이 사라졌다. 이로써 사용자가 객체의 마무리 작업을 직접 실행하지 않아도 된 것이다.
주의할 점
객체 생성자와 마찬가지로 객체 파괴자도 생략이 가능하다. 객체 생성자는 객체를 생성하는 시점이 명확하므로 매개변수를 옆에 붙이는 형식으로 여러 개의 객체 생성자를 만들 수 있었다. 하지만, 객체 파괴자의 경우는 객체가 언제 파괴되는지 사용자 입장에서는 알 수 없다. 그렇기 때문에 별도의 호출을 통해 매개변수를 전달하기가 어렵다. 즉, 객체 생성자처럼 여러 개의 객체 파괴자를 만들 수 없다는 의미이다. 그렇다고 객체 파괴자를 직접 호출할 수 없다는 말은 아니다. 아래의 예와 같이 별도의 호출이 가능하다.
str.~MyString();
하지만, 이는 객체 파괴자를 호출했다고 생각하기 보다는 객체의 멤버 함수를 호출했다고 생각하는게 맞다. 왜냐하면 위처럼 객체 파괴자를 명시적으로 호출하여도 객체가 파괴되는 시점에 객체 파괴자가 한번 더 실행되기 때문이다.
'C++ > C++ 기초' 카테고리의 다른 글
클래스 외부에 멤버 함수 구현하기 (0) | 2019.09.11 |
---|---|
네임스페이스 (Namespace) (0) | 2019.09.09 |
객체 생성자 (0) | 2019.09.02 |
오버로딩(Overloading) (0) | 2019.09.01 |
클래스(Class) (0) | 2019.08.31 |