CPPのvirtualデストラクタ

2. Kaleidoscope: Implementing a Parser and AST — LLVM 7 documentationでvirtual修飾子のついたデストラクタに出会った。いままで知らなかった(もしくは、忘れていた)ので、メモ。c++ - When to use virtual destructors? - Stack Overflowを参考にした。

#include <iostream>

class Base {
public:
    ~Base() { std::cout << "destructor at Base" << std::endl; }
};

class Sub : public Base {
    ~Sub() { std::cout << "destructor at Sub" << std::endl; } 
};

int main()
{
    Base *s = new Sub();
    delete s;
    return 0;
}
destructor at Base

一方、Baseのデストラクタにvirtual修飾子をつけることで、

#include <iostream>

class Base {
public:
    virtual ~Base() { std::cout << "destructor at Base" << std::endl; }
};

class Sub : public Base {
public:
    ~Sub() { std::cout << "destructor at Sub" << std::endl; } 
};

int main()
{
    Base *s = new Sub();
    delete s;
    return 0;
}
destructor at Sub
destructor at Base

とでき、デストラクターがサブクラスから順に呼ばれている。このように、オブジェクトを多相的に(ベースクラスオブジェクトとして)使いたいときは、ベースクラスのデストラクタにvirtual修飾子をつけておかないと、サブクラスのデストラクタが呼ばれない。

ちなみに、virtual修飾子がついた関数(=オーバーライドできる関数)を仮想関数と呼ぶらしい。ただ、デストラクターは特殊なオーバーライドになっている。普通のオーバーライドは次のように、

class Base {
public:
    virtual void hoge() { std::cout << "hope at Base" << std::endl; }
};

class Sub : public Base {
public:
    void hoge() { std::cout << "hoge at Sub" << std::endl; } 
};

int main()
{
    Base *s = new Sub();
    s->hoge();
    return 0;
}
hoge at Sub

と、オーバーライドした関数のみが呼ばれるが、デストラクタの場合はオーバーライドしていると言うより、コールの連鎖を作っていると言った方が近い?