소년코딩

소멸자 (Destructor)

소멸자는 객체가 소멸될 때 자동으로 실행되는 클래스의 멤버 함수다. 생성자는 클래스의 초기화를 돕도록 설계됐지만 소멸자는 청소를 돕도록 설계되었다.

지역에서 생성된 객체가 지역 범위를 벗어나거나 동적으로 할당된 객체가 삭제 키워드를 사용해 명시적으로 삭제되면, 객체가 메모리에서 제거되기 전에 필요한 정리를 수행하기 위해 클래스는 소멸자가 있는 경우 소멸자를 자동으로 호출한다.

클래스의 멤버 변수들이 단순하게 기본 자료형이 값 형식이라면 크게 필요 없지만 다른 리소스(예: 동적 메모리, 파일 또는 데이터베이스 핸들러)라면 객체가 소멸되기 전에 어떤 종류의 유지보수를 해야 한다. 이때 소멸자는 객체가 소멸되기 전에 마지막으로 호출되는 특별한 함수이므로 완벽한 장소가 된다.


소멸자 규칙

생성자처럼 소멸자도 특별한 규칙이 있다.

  1. 소멸자 이름은 클래스 이름과 같아야 하고 앞에 ~를 달아야 한다.
  2. 소멸자는 인수가 없다.
  3. 소멸자는 반환 값이 없다.

이런 규칙 때문에 소멸자는 클래스당 하나밖에 존재할 수 없다.

또한, 소멸자를 명시적으로 호출하는 경우는 없다.


소멸자 사용 예제

소멸자를 사용하는 간단한 클래스를 살펴보자:

#include <iostream>
#include <cstddef>

using namespace std;

class IntArray
{
private:
    int* m_Array;
    int m_Length;

public:
    IntArray(int length) // 생성자
    {        
        m_Array = new int[static_cast<size_t>(length)]{};
        m_Length = length;
    }

    ~IntArray() // 소멸자
    {
        // 동적으로 할당한 배열을 삭제한다.
        delete[] m_Array;
    }

    void SetValue(int index, int value)
    {
        m_Array[index] = value;
    }

    int GetValue(int inudex)
    {
        return m_Array[index];
    }

    int GetLength()
    {
        return m_Length;
    }
};

int main()
{
    IntArray ar(10);

    for(int count = 0; count < ar.GetLength(); ++count)
    {
        ar.SetValue(count, count + 1);
    }

    cout << "The value of element 5 is: " << ar.getValue(5) << '\n';
    // The value of element 5 is: 6

    return 0;
} // ar 객체는 여기서 삭제되므로 ~IntArray() 소멸자 함수는 여기서 호출된다.
  • IntArray ar(10);에서 ar라는 새로운 IntArray 클래스의 객체를 인스턴스화 하고 10의 길이를 가진 배열을 동적 할당한다.

  • main() 함수의 끝에서 ar 객체는 스코프 범위를 벗어난다. 이로 인해 ~IntArray() 소멸자가 호출되어 생성자에서 할당한 배열이 삭제된다.


생성자 및 소멸자의 생명주기

지금까지 설명한 것처럼 객체가 생성되는 시점에는 생성자를, 소멸되는 시점에는 소멸자를 호출한다. 다음 예에서는 생성자와 소멸자 내부에서 cout를 이용해 이를 더 자세하게 알 수 있다:

class Simple
{
private:
    int m_ID;

public:
    Simple(int id) : m_ID(id)
    {
        cout << "Constructing Simple " << m_ID << endl;
    }

    ~Simple()
    {
        cout << "Destructing Simple " << m_ID << endl;
    }

    int GetID()
    {
        return m_ID;
    }
};

int main()
{
    // 스택에 단순 할당
    Simple simple(1);
    cout << simple.GetID() << endl;

    // 동적으로 할당
    Simple* pSimple = new Simple(2);
    cout << pSimple->GetID() << endl;

    // pSimple을 동적으로 할당했으므로 삭제해야 한다.
    delete pSimple;
}

이 프로그램을 실행하면 아래와 같은 결과를 볼 수 있다:

Constructing Simple 1        // 스택 영역에 단순 할당된 simple 객체(인스턴스)의 생성자
1
Constructing Simple 2        // 힙 엽역에 동적 할당된 pSimple 객체(인스턴스)의 생성자
2
Destructing Simple 2         // 명시적으로 pSimple 객체 삭제 -> pSimple 객체(인스턴스)의 소멸자
Destructing Simple 1         // main 함수의 스택을 벗어 났으므로 simple 객체 삭제 -> simple 객체의(인스턴스)의 소멸자

main 함수가 끝나기 전에 pSimple명시적으로 삭제했으므로 'Destructing Simple 2' 출력문을 먼저 볼 수 있다.


RAII

RAII (Resource Acquisition Is Initialization)는 객체의 수명과 리소스 관리와 연관있는 프로그래밍 디자인 기법이다. C++ 에서 RAII는 클래스의 생성자와 소멸자로 구현되었다.

  • 리소스(메모리, 파일 또는 데이터베이스 핸들 등)는 일반적으로 객체의 생성자에서 획득된다.
  • 그러면 그 리소스는 객체가 살아있는 동안 사용될 수 있다.
  • 객체가 소멸하면 자원이 소멸된다.

RAII의 장점은 리소스를 보유한 객체가 자동으로 정리됨에 따라 리소스 누수(예: Memory Leak)를 방지하는 데 도움이 된다는 것이다.

이 포스트의 맨 위에 있는 IntArray 클래스는 RAII를 구현하는 클래스의 예제로서 생성자에서의 할당, 소멸자에서의 할당 해제를 포함한다. 나중에 설명할 std::stringstd::vector는 RAII를 따르는 표준 라이브러리의 클래스로서 동적 메모리는 초기화 시 획득하고, 파괴 시 자동으로 정리된다.


요약

생성자와 소멸자를 함께 사용하면 프로그래머가 특별한 작업을 하지 않아도 클래스는 스스로 초기화하고 정리할 수 있다. 이렇게 하면 오류가 발생할 확률을 낮추고 클래스를 더 쉽게 사용할 수 있다.


cpp 번역: 이 포스트의 원문은 https://www.learncpp.com/cpp-tutorial/8-7-destructors/ 입니다.

댓글 로드 중…

블로그 정보

소년코딩 - 소년코딩

소년코딩, 자바스크립트, C++, 물리, 게임 코딩 이야기

최근에 게시된 이야기