派生クラスの型を既定クラスに渡す

前回(id:wata_d:20050723:1122066377)軽く紹介した、「派生クラスの型を基底クラスに渡す」というイディオムについてちょっとだけ書いてみます。

template<class T>
struct Base
{
    void Execute()
    {
        static_cast<T*>(this)->Print();
    }

    void Print()
    {
        std::cout << "Base" << std::endl;
    }
};

struct Derived : public Base<Derived>
{
    void Print()
    {
        std::cout << "Derived" << std::endl;
    }
};

void test()
{
    Derived d;
    d.Execute();
}

相変わらず解りにくい例ですが……。test関数の中で、Derived型の変数を作成し、そのExecuteメソッドを呼び出します。Derived::Executeは定義されていないため、Base::Executeが呼び出されます。ここでさらに自分のメンバであるPrintメソッドを呼び出すわけですが、Printメソッドは仮想関数ではない(virtual修飾子がない)ため本来であればBase::Printが呼び出されて、実行すると画面には「Base」という文字列が出力されるはずです。

しかし、このコードを実際に実行すると、画面には「Derived」が出力されます。肝は Base::Execute の中のキャスト部分。T型はBaseクラスのテンプレート引数として、派生クラスから渡される、派生クラス自身の型(ここではDerivedが相当する)になっており、このキャスト時に一時的に自分自身を派生クラスの型に変換してからその型のPrint(つまりDerived::Print)を呼び出すことによって、virtualを介さずに仮想関数のようなことができるようになっています。

まあ、素直にvirtual使ってやった方が楽なんですが、vtableを作りたくない時などに使えたりするので、覚えておいて損はないのではないかと思います。