ゲーム作りは楽しい

なんか書く

C++:非virtualデストラクタ継承の話

デストラクタがvirtualでないclassの継承には注意が必要という話を聞いたことがある人もいると思いますがその話をちょっとします

struct A
{
    A() = default;

    ~A() 
    {
            std::cout << "A destructor" << std::endl;
    }
};

struct B:A
{
    B() = default;

    ~B()
    {
        std::cout << "B destructor" << std::endl;
    }
};

int main()
{
    {
        std::unique_ptr<B> a = std::make_unique<B>();
    }
    {
        std::unique_ptr<A> a = std::make_unique<B>();
    }
    return 0;
}

いまAのデストラクタはvirtualではありません。
このときの出力は

B destructor
A destructor
A destructor

になります。

非virtualデストラクタを持つ型からアップキャストした場合、破棄されるときに呼ばれるのは 基底classのデストラクタのみ になるので注意。
じゃあ基底classのデストラクタをvitualにすればいいじゃん? 確かにそうですがたとえば
std::vectorなどのような標準ライブラリのclassなどで変更がきかない場合もあります。
でも、vectorを拡張したいとか思ったことありませんか?

アップキャストせずに使えばもちろん問題はないですがそれでも間違えてやってしまう場合もないとはいえません。
(派生先のデストラクタが何もしてないなら最悪呼ばれなくてもいいっちゃいいのかな?)
そういう時、より安全に使うためにはprotected継承してあげるのがよくある例だったりします。

struct A
{
    A() = default;

    ~A() 
    {
            std::cout << "A destructor" << std::endl;
    }
};

struct B:protected A
{
    B() = default;

    ~B()
    {
        std::cout << "B destructor" << std::endl;
    }
};

int main()
{
    {
        std::unique_ptr<B> a = std::make_unique<B>();
    }
    {
        //std::unique_ptr<A> a = std::make_unique<B>(); これがコンパイルエラーになる
    }
    return 0;
}

こうすることで、そもそもアップキャストして使うことそのものをエラーにできます。 ちなみに、Siv3Dのs3d::Arraystd::vectorをprotected継承してます。

最後に、protected継承したということは、基底classでpublicだったものがすべてprotectedになるので 必要なものはusingしてあげるといいでしょう。

struct A
{
    A() = default;

    ~A()
    {
        std::cout << "A destructor" << std::endl;
    }
    void hoge() {}
};

struct B :protected A
{
    B() = default;

    ~B()
    {
        std::cout << "B destructor" << std::endl;
    }

    using A::hoge;

};