間が空きましたが、前回(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でした。なお、この手法を推奨しているわけではありません。念のため。

スポンサード リンク

この記事のカテゴリ

  • ⇒ Visual C++の不思議なfriend(その2)
  • ⇒ Visual C++の不思議なfriend(その2)