Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

nomad-programmer

[Programming/C++] 가상 소멸자(Virtual Destructor) 본문

Programming/C++

[Programming/C++] 가상 소멸자(Virtual Destructor)

scii 2021. 2. 2. 01:13

가상함수 말고도 virtual 키워드를 붙여줘야 할 대상이 하나 더 있다. 그것은 바로 '소멸자'이다. 

즉, virtual 선언은 소멸자에도 올 수 있다.

가상 소멸자(Virtual Destructor)

virtual로 선언된 소멸자를 가리켜 '가상 소멸자'라 부른다. 다음의 예를 살펴보자.

#include <iostream>
#include <cstring>

#pragma warning(disable: 4996)

using std::cout;
using std::cin;
using std::endl;


class First {
private:
    char* strOne;

public:
    explicit First(const char* str) {
        cout << "First" << endl;
        strOne = new char[strlen(str) + 1];
    }
    ~First() {
        cout << "~First()" << endl;
        delete[] strOne;
    }
};

class Second : public First {
private:
    char* strTwo;

public:
    explicit Second(const char* str1, const char* str2) : First(str1) {
        cout << "Second" << endl;
        strTwo = new char[strlen(str2) + 1];
    }
    ~Second() {
        cout << "~Second()" << endl;
        delete[] strTwo;
    }
};


int main(const int argc, const char* const argv[]) {
    First* ptr = new Second("aaa", "bbb");

    delete ptr;

    return 0;
}

/* 결과

First
Second
~First()

/*

실행결과에서도 보이듯이 객체의 소멸을 First형 포인터로 명령하니, First클래스의 소멸자만 호출되었다. 따라서 이러한 경우에는 메모리의 누수(leak)가 발생하게 된다. 그러니 객체의 소멸과정에서는 delete 연산자에 사용된 포인터 변수의 자료형에 상관없이 모든 소멸자가 호출되어야 한다. 그리고 이를 위해서는 다음과 같이 소멸자에 virtual 선언을 추가하면 된다.

virtual ~First() {
  cout << "~First()" << endl;
  delete[] strOne;
}

가상함수와 마찬가지로 소멸자도 상속의 계층구조상 맨 위에 존재하는 기초 클래스의 소멸자만 virtual로 선언하면, 이를 상속하는 파생 클래스의 소멸자들도 모두 '가상 소멸자'로 선언이 된다.
그리고 가상 소멸자가 호출되면, 상속의 계층구조상 맨 아래에 존재하는 파생 클래스의 소멸자가 대신 호출되면서, 기초 클래스의 소멸자가 순차적으로 호출된다.

class First {
private:
    char* strOne;

public:
    explicit First(const char* str) {
        cout << "First" << endl;
        strOne = new char[strlen(str) + 1];
    }
    virtual ~First() {
        cout << "~First()" << endl;
        delete[] strOne;
    }
};

class Second : public First {
private:
    char* strTwo;

public:
    explicit Second(const char* str1, const char* str2) : First(str1) {
        cout << "Second" << endl;
        strTwo = new char[strlen(str2) + 1];
    }
    virtual ~Second() {
        cout << "~Second()" << endl;
        delete[] strTwo;
    }
};


int main(const int argc, const char* const argv[]) {
    First* ptr = new Second("aaa", "bbb");

    delete ptr;

    return 0;
}

/* 결과

First
Second
~Second()
~First()

*/

First클래스의 포인터 ptr은 Second클래스의 메모리를 가리키고 있다. Second클래스는 First클래스를 상속받아 확장된 클래스이므로 First클래스의 포인터 ptr은 해당 메모리에서 First클래스 범위의 영역만 가리키게된다. 
따라서 ptr포인터로 Second클래스의 소멸자를 호출할 수 없다. Second클래스의 소멸자를 호출하려면 First클래스의 소멸자를 virtual키워드로 '가상 소멸자'로 만들어 Firtst클래스의 소멸자가 호출되면 Second클래스의 소멸자가 호출되도록 해야 한다.
이렇게 되면 Second클래스의 소멸자가 호출되면서 메모리에서 제거되고 상속받은 클래스가 있으므로 해당 클래스의 소멸자를 호출되면서 메모리에서 제거된다. 결과적으로 모든 소멸자가 호출되면서 메모리에서 제거된다.

다음의 예를 보자.

class First {
public:
    virtual ~First() {};
};

class Second : public First {
public:
    virtual ~Second() {}
};

class Third : public Second {
public:
    virtual ~Third() {}
};


int main(const int argc, const char* const argv[]) {
    First* ptr = new Third();

    delete ptr;

    return 0;
}

가상 소멸자의 호출과정

virtual로 인하여 [~Third() 소멸자 호출] → [~Second() 소멸자 호출] → [~First() 소멸자 호출] 이렇게 실행이 이어진다.

Comments