1. 導入:なぜ「型消去」が必要なのか?
C++で「異なる型のオブジェクトを同じリストで扱いたい」と思ったとき、真っ先に思い浮かぶのは基底クラスと仮想関数を使った継承でしょう。しかし、継承には「既存のクラスを継承させなければならない」「オブジェクトの所有権が複雑になる」といった制約が伴います。
そこで登場するのが「型消去(Type Erasure)」という技術です。これを使うと、継承関係のないバラバラな型を、あたかも同じ型のオブジェクトのように扱うことができます。`std::function` や `std::any` の裏側でも使われている、C++中級者へのステップアップに欠かせないテクニックです。
2. 基礎知識:型消去の仕組み
型消去の核となる考え方は、「具体的な型情報を外から見えないように隠し、共通の操作だけを関数ポインタのテーブル(手動Vtable)として保持する」という点にあります。
本来、C++のコンパイラは型を厳密にチェックしますが、テンプレートコンストラクタで任意の型を一度受け取り、その型特有の処理(描画、計算など)を「関数ポインタの箱」に詰め替えることで、コンパイラから特定の型情報を消去(Erasure)します。これにより、利用者側は元の型を意識せずにオブジェクトを操作できるようになります。
3. 実装と解決策:手動Vtableの構築
実装のステップは以下の通りです。
1. 実行したい操作をラップする構造体(コンセプト)を定義する。
2. テンプレートを使って、任意の型をその操作に適合させる(モデル)。
3. 外部クラスで、それらの操作を呼び出すための関数ポインタを保持する。
これにより、継承関係を強制することなく、動的なポリモーフィズムを実現できます。
4. サンプルプログラム
以下のコードは、異なる型に対して同じ「挨拶(say_hello)」をさせるシンプルな型消去の実装例です。
include
include
include
// 1. 共通の操作を定義するインターフェース(手動Vtable)
struct Concept {
virtual ~Concept() = default;
virtual void say_hello() const = 0;
};
// 2. 任意の型をラップするテンプレートクラス
template
struct Model : Concept {
T data;
Model(T val) : data(val) {}
void say_hello() const override {
// 型Tが持っている処理を呼び出す
data.say_hello();
}
};
// 3. 利用者が触れるラッパークラス
class Greeter {
std::unique_ptr
public:
template
Greeter(T obj) : ptr(std::make_unique
void say_hello() const {
ptr->say_hello();
}
};
// 使用例
struct Japanese { void say_hello() const { std::cout << "こんにちは" << std::endl; } };
struct English { void say_hello() const { std::cout << "Hello" << std::endl; } };
int main() {
// 継承関係がないクラスを同じGreeterとして扱える
Greeter g1 = Japanese();
Greeter g2 = English();
g1.say_hello(); // こんにちは
g2.say_hello(); // Hello
return 0;
}
5. 応用と注意点
型消去のメリットは、サードパーティ製のライブラリなど、継承させることができないクラスをそのままポリモーフィズムの枠組みに組み込める点にあります。
注意点として、仮想関数を介するため、インライン展開が制限され、純粋なテンプレートコードよりも実行速度が低下する可能性があります。また、`std::unique_ptr` を使うとメモリ確保が発生するため、性能がシビアな現場では、SSO(Small Object Optimization:小さなオブジェクトならメモリ確保をせずバッファ上に直接配置する最適化)を自作のバッファで実装するなどの工夫が重要になります。まずはこのコードで「型を消去する」感覚を掴んでみてください。

コメント