スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

テンプレート化されたクラスの仮想継承

まずは、ソースを見ていただこう。ABCクラスがAとBの共通の親で、CがAとBを多重継承しているという、いわゆる菱形継承ダイアグラムであるが、これにテンプレートがからんでいる。

ABC.h:

#ifndef ABC_H
#define ABC_H

template <class T> class ABC {
public:
ABC();
};
#endif

ABC.cpp:

#include "ABC.h"
#include <iostream>

template <class T> ABC<T>::ABC()
{
std::cout<<"ABC::ABC()" << std::endl;
}

namespace ABC_Template {
void instantiateTemplates(){
ABC<int> abc;
}
};

A.h:

#ifndef A_H
#define A_H

#include "ABC.h"

template <class T> class A : virtual public ABC<T>
{
public:
A();
};

#endif

A.cpp:

#include "A.h"
#include <iostream>

template <class T> A<T>::A()
: ABC<T>()
{
std::cout << "A::A()" << std::endl;
}

namespace A_Template {
void instantiateTemplates(){
A<int> a;
}
};


B.h:

#ifndef B_H
#define B_H

#include "ABC.h"

template <class T> class B : virtual public ABC<T>
{
public:
B();
};
#endif

B.cpp:

#include "B.h"
#include <iostream>

template <class T> B<T>::B()
: ABC<T>()
{
std::cout << "B::B()" << std::endl;
}

namespace B_Templates {
void instantiateTemplates(){
B<int> b;
}
};

C.h:

#ifndef C_H
#define C_H

#include "A.h"
#include "B.h"

template <class T> class C : public A<T>, public B<T>
{
public:
C();
};

#endif

C.cpp:

#include <iostream>
#include "C.h"

template <class T> C<T>::C()
: A<T>(), B<T>()
{
std::cout << "C::C()" << std::endl;
}

namespace C_Templates {
void instantiateTemplates(){
C<int> c;
}
};

main.cpp:

#include "C.h"

int main()
{
C<int> c;
return 0;
}


これを、

g++ -o test main.cpp C.cpp A.cpp B.cpp ABC.cpp

とすると、

/tmp/cczOxvhL.o: In function `C::C()':
C.cpp:(.text._ZN1CIiEC1Ev[C::C()]+0x2c): undefined reference to `A::A()'
C.cpp:(.text._ZN1CIiEC1Ev[C::C()]+0x48): undefined reference to `B::B()'
collect2: ld はステータス 1 で終了しました

のように、中間クラスのコンストラクタのリンクに失敗する。なお、テンプレートを用いていなければ、上の例は問題なくコンパイルできる。それは各クラスのcppファイルの中で、リンク時に必要になる具体的な関数のインスタンスをあらかじめ明示的に作成しているからである。各ファイル中で、namespaceで囲んだinstantiateTemplates()という関数がそれである。明示的に呼び出さない限り、この部分のコードが実行されることはないが、(テンプレートを用いていない場合は少なくとも)リンクはされる。

で、テンプレートを用いた場合、これだけではうまくいかない訳であるが、man g++でテンプレートに関係のある部分を見てみたところ、-frepo というオプションが、リンク時にテンプレートの自動インスタンス化を行ってくれるとある。ただし、このオプションを指定するのは-cオプションでオブジェクトを生成するフェーズのときのようである。

そこで、以下のように簡単なMakefileを書いてみる。

Makefile:
CXXFLAGS = -frepo
OBJECTS = main.o A.o B.o C.o ABC.o

all:test

test: $(OBJECTS)
g++ -o test $(OBJECTS)

これで、makeを実行してみると、

$make
g++ -frepo -c -o main.o main.cpp
g++ -frepo -c -o A.o A.cpp
g++ -frepo -c -o B.o B.cpp
g++ -frepo -c -o C.o C.cpp
g++ -frepo -c -o ABC.o ABC.cpp
g++ -o test main.o A.o B.o C.o ABC.o
collect: ABC.cpp を再コンパイルしています
collect: B.cpp を再コンパイルしています
collect: A.cpp を再コンパイルしています
collect: C.cpp を再コンパイルしています
collect: 再リンクしています
collect: B.cpp を再コンパイルしています
collect: A.cpp を再コンパイルしています
collect: main.cpp を再コンパイルしています
collect: ABC.cpp を再コンパイルしています
collect: 再リンクしています
collect: main.cpp を再コンパイルしています
collect: 再リンクしています
collect: main.cpp を再コンパイルしています
collect: 再リンクしています
collect: main.cpp を再コンパイルしています
collect: 再リンクしています

のように、どうやらコンパイラが必要なテンプレートのインスタンスをうまく生成しつつ、リンクを行ってくれているようである。ちゃんと

./test
ABC::ABC()
A::A()
B::B()
C::C()

のように実行できた。

2013/02/13 追記
テンプレートのインスタンス生成用関数を独立したネームスペース内に閉じ込めてしまうと、後でリンクエラーを引き起こすことがあるようだ(g++ 4.6での場合)。コンパイラー、リンカーの挙動としてはどうなるのが正しいのか知識不足で分からないが、とりあえずはテンプレートインスタンス生成用の関数はグローバルネームスペース内に配置するのがよいようだ。当然関数名の衝突の危険が出てくるから、ソースファイル名を接頭辞にするなどの対応が必要になる。


Delicious Save this on Delicious
スポンサーサイト
プロフィール

GM3D

Author:GM3D
FC2ブログへようこそ!

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
FC2カウンター
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。