間が空きましたが、前回(friendとtemplateによるVisual C++でしか動かないコード)の続きです。
今度は分割コンパイルです。前回消化した謎の挙動とテンプレートの明示的実体化を使って、オブジェクトファイル間の依存を断ち切るコードを書きます。
まずは、2ファイル中1つ目、実装を書く側です。
#include <iostream> void f() { std::cout << "f" << std::endl; } void g() { std::cout << "g" << std::endl; } template<class T> class TypeF { friend void InvokeImpl(T*) { f(); } public: // 前回からの追加部分 void Invoke() { InvokeImpl(static_cast<T*>(nullptr)); }; }; template<class T> class TypeG { friend void InvokeImpl(T*) { g(); } public: // 前回からの追加部分 void Invoke() { InvokeImpl(static_cast<T*>(nullptr)); }; }; struct Hoge; struct Piyo; struct Fuga; template class TypeF<Hoge>; template class TypeG<Piyo>; template class TypeF<Fuga>; |
そしてもう1つのファイル、それを使う側です。
template<class T> class Selector { friend void InvokeImpl(T*); public: static void Invoke() { return InvokeImpl(static_cast<T*>(nullptr)); } }; // ここから利用例 struct Hoge; struct Piyo; struct Fuga; int main() { Selector<Hoge>::Invoke(); Selector<Piyo>::Invoke(); Selector<Fuga>::Invoke(); } |
前回との違いはメンバ関数Invokeです。テンプレートTypeFとTypeGの実体化でInvokeが実体化、Invoke内で呼び出すフレンド関数InvokeImplも実体化されるという流れです。
これの特長はオブジェクトファイルの依存関係を絶縁(分離)できることです。それを利用する側(この例ではmain関数)の翻訳単位は、InvokeImplの実装を全く知らずコンパイルできます。
たとえば、template class TypeF<Fuga>;をtemplate class TypeG<Fuga>;に変えても利用する側の再コンパイルは不要というわけです。
というわけで、Visual C++の不思議な挙動その2でした。なお、この手法を推奨しているわけではありません。念のため。
スポンサード リンク |