C++ で CRTP を使用して、仮想メンバー関数のオーバーヘッドを回避するにはどうすればよいですか?
5 に答える
2つの方法があります。
1つ目は、型の構造に対して静的にインターフェースを指定することです。
template <class Derived>
struct base {
void foo() {
static_cast<Derived *>(this)->foo();
};
};
struct my_type : base<my_type> {
void foo(); // required to compile.
};
struct your_type : base<your_type> {
void foo(); // required to compile.
};
2つ目は、ベースへの参照またはベースへのポインターのイディオムの使用を避け、コンパイル時に配線を行うことです。上記の定義を使用すると、次のようなテンプレート関数を使用できます。
template <class T> // T is deduced at compile-time
void bar(base<T> & obj) {
obj.foo(); // will do static dispatch
}
struct not_derived_from_base { }; // notice, not derived from base
// ...
my_type my_instance;
your_type your_instance;
not_derived_from_base invalid_instance;
bar(my_instance); // will call my_instance.foo()
bar(your_instance); // will call your_instance.foo()
bar(invalid_instance); // compile error, cannot deduce correct overload
したがって、構造体/インターフェイスの定義とコンパイル時の型の推定を関数で組み合わせると、動的ディスパッチの代わりに静的ディスパッチを実行できます。これが静的ポリモーフィズムの本質です。
私はCRTPについてのまともな議論を自分で探していました。ToddVeldhuizenのScientificC++のテクニックは、この(1.3)および式テンプレートのような他の多くの高度なテクニックの優れたリソースです。
また、GoogleブックスでCoplienのオリジナルのC++Gemsの記事のほとんどを読むことができることがわかりました。多分それはまだそうです。
CRTPを調べる必要がありました。しかし、それを行った後、Static Polymorphismに関するいくつかの情報を見つけました。これがあなたの質問に対する答えだと思います。
ATLはこのパターンを非常に広範囲に使用していることがわかりました。
このウィキペディアの回答には、必要なものがすべて含まれています。すなわち:
template <class Derived> struct Base
{
void interface()
{
// ...
static_cast<Derived*>(this)->implementation();
// ...
}
static void static_func()
{
// ...
Derived::static_sub_func();
// ...
}
};
struct Derived : Base<Derived>
{
void implementation();
static void static_sub_func();
};
これが実際にどれだけあなたを買うかはわかりませんが。仮想関数呼び出しのオーバーヘッドは次のとおりです (もちろん、コンパイラに依存します)。
- メモリ: 仮想関数ごとに 1 つの関数ポインター
- ランタイム: 1 つの関数ポインター呼び出し
CRTP 静的ポリモーフィズムのオーバーヘッドは次のとおりです。
- メモリー: テンプレートのインスタンス化ごとの Base の複製
- ランタイム: 1 つの関数ポインター呼び出し + static_cast が行っていることすべて