2013-03-12 7 views
6

テンプレートの宣言と定義を分離する中規模または大規模な複雑なプロジェクトでは、コンパイル時間を短縮するのに便利です 分離されたヘッダ/ソースのためのC++でのテンプレート特化の堅牢な方法

しかし、複雑なコードでは、小さなプログラマーの間違いが気付かれない振る舞いの変化につながる可能性があります。 特殊化の代わりに汎用バージョンが呼び出されます。

例: 宣言が欠落してテンプレートの特殊化が見えなくなりました。

///////////////////// file A.hpp ///////////////////// 

#include <iostream> 

template <typename T> 

class A 
{ 
public: 
    void foo() 
    { 
     std::cerr << " calling generic foo " << std::endl ; 
    } 
}; 

// forgetting following declaration leads to an unintended program behaviour 
template <> void A<int>::foo(); 

///////////////////// file A-foo-int.cpp ///////////////////// 
#include "A.hpp" 

template <> 
void A<int>::foo() 
{ 
    std::cerr << "calling <int> version of foo" << std::endl; 
} 

///////////////////// file main.cpp ///////////////////// 
#include "A.hpp" 

int main(int argc , char** argv) 
{ 
    A<int>* a = new A<int>(); 
    a->foo(); 
    return 0; 
} 

///////////////////// Makefile ///////////////////// 
CC = g++ 
CPPFLAGS += -Wall -O3 
CXXFLAGS += --std=gnu++0x 

all: nonrobust-template-setup 

nonrobust-template-setup: main.o A-foo-int.o 
    $(CC) $(CPPFLAGS) main.o A-foo-int.o -o nonrobust-template-setup 

clean: 
    rm -rf *.o nonrobust-template-setup 

////////////////////////////////////////// 

質問: は(compiler-とプラットフォームに依存しない)可能性がより堅牢なセットアップで とあれば、どのようにそれは次のようになりますか?

もしそうでない場合は、目的の関数バージョンが呼び出されたことをテストする良い方法はありますか?

+0

私はあなたがすべきことをすべてやっていると思います。それはちょうどそれがC++で動作する方法です: – cubuspl42

答えて

2

特殊なメンバ関数の定義を個別の.cppファイルに格下げすると、プライマリテンプレートの直後に特殊化を宣言しても、コンパイラは次のように宣言することができません。それをインスタンス化すると、リンカーは未解決の参照について不平を言うでしょう。

あなたは、対応するクラステンプレートの明示的なインスタンス化提供ない限り、通常、クラステンプレートのメンバ関数の定義はヘッダファイル、に行く:あなたがない限り、一般的には

template class X<int>; // You should add this to make your program build, 
         // or instantiate the corresponding class template 
         // specialization and invoke the foo() method in the 
         // same translation unit (A.cpp) 

を面白いコンパイル時に問題が起きたら、私はあなたに共通の習慣に従って、クラステンプレートを使用する必要があるすべての翻訳単位に含めるためにすべてをヘッダファイルに入れてください:

///////////////////// file A.hpp ///////////////////// 

#include <iostream> 

template <typename T> 

class A 
{ 
public: 
    void foo() 
    { 
     std::cerr << "error: called generic foo " << std::endl ; 
    } 
}; 

template <> 
void A<int>::foo() 
{ 
    std::cerr << "calling <int> version of foo" << std::endl; 
} 

///////////////////// file main.cpp ///////////////////// 
#include "A.hpp" 

int main(int argc , char** argv) 
{ 
    A<int>* a = new A<int>(); 
    a->foo(); 
    return 0; 
} 

本当に恐ろしいコンパイル時間の問題に直面している場合は、メンバー関数定義を分離して、明示的なインスタンス化を伴う別々の翻訳単位に入れることができますが、C++ 11では、独立した.cppファイル内のすべての専門分野は、プライマリテンプレートの直後に宣言されていることが推奨されています。もし皆さんがここに来て尋ねる必要がないほど人気が​​あると思います。皆様はこのようなデザイン上の問題に直面しています。

場合によっては、いくつかのファンシーマクロが役立つかもしれませんが、本当に複雑なプロジェクトでメンテナンスの痛みよりも多くのメリットをもたらすことは間違いありません。

exportキーワードを導入して、この問題の解決策をC++ 03標準で試しましたが、実装経験により、コンパイラベンダーのサポートが難しくなりました。そのため、exportはC++標準の一部ではありませんC++ 11以降)。

モジュールのより良い解決策は、それをC++ 14にして、テンプレートデザインのソリューションを提供することを望みます。

+0

OK、正しいメソッドのバージョンが呼び出されるかどうかを確認するテストを書く以外は、この問題のための良い解決策はありません。 –

+0

@ JarobKroeker:はい、基本的には私が伝えようとした印象です。私はまだこの問題の良い解決策はないと信じています。 –

1

私ができることは、static_assertには、汎用テンプレートが特殊化されていると思われるタイプでインスタンス化されないことです。

次のコードは、私がおそらくBOOST_STATIC_ASSERT(そして、C++ 11を使用できる場合はstd::is_same)を使用しています。基本的な考え方は、禁止するタイプのセットを使用して非特化テンプレートを暗黙的にインスタンス化するのを防ぐことです。もちろん、静的なアサーションと特殊化を追加するのを忘れても、あなたはまだ失敗するでしょう。これを確認するために

template<class T, class U> 
struct is_same { enum { value = false }; }; 

template<class T> 
struct is_same<T, T> { enum { value = true }; }; 

template <bool enable> 
struct StaticAsserter 
{ 
    char test[enable]; 
}; 

template <typename T> 
struct foo 
{ 
    // Make sure we can't implicit instantiate foo for int. 
    StaticAsserter<!is_same<int, T>::value> DisallowInt; 
}; 

int main() 
{ 
    foo<unsigned> hi; 
    foo<int> fail; 

    return 0; 
} 
+0

私が質問を正しく理解していれば、OPはプライマリテンプレートの直後に彼が後で定義する明示的な特殊化を宣言することを忘れないようにしたいと考えています。この場合、static_assertヘルプはどのように役立ちますか? –

+0

OK、小規模なプロジェクトの一つに、おそらく次の手順を実行できます。 'テンプレート クラス { 公共: 無効のfoo(){ static_assert(STDない:: is_same ::値、「int型の分業for foo()はインスタンス化されていません! "); } }; しかし、これは中程度の規模のプロジェクトでは実行できません。 –

0

方法は、汎用テンプレートのfoo()のいずれかの定義を提供しないことです。あなたはそれをこのようにやっている時に特殊化を宣言する必要はありません:

// A.h 
template <typename T> struct A { void foo(); }; 
// main.cc 
#include "A.h" 
int main (int c, char **v) 
{ 
    A<int>().foo(); 
    // A<long>().foo(); // this line will compile but not link 
} 
// A.cc 
#include <cstdio> 
#include "A.h" 
template<> void A<int>::foo() { puts("foo!"); } 
+0

いくつかのケースでは、一般的な実装が必要で有用なので、削除できません。 –

+0

あなたの提供したコード "error:generic foo"はジェネリックの実装では他に信じられました。おそらく他人の質問を明確にするのにも役立ちます。 – jthill

+0

Re:あなたの提供したコード "error:generic foo"実装はそうでなければ信じるように私を導いた - はい、それは本当に、申し訳ありません。多分私はコード例を編集する必要があります(可能であれば) –

0

さて、ジェネリックA<T>::foo()実装がだけならば、必ずしもエラーではないインスタンスのコメントから他の分野に特化しています。

したがって、コンパイラオブジェクトファイルの特定のリストでインスタンス化されている必要がある特殊化の名前が2つのデータセットの一致するフィールドを探すのに必要な汎用テンプレートインスタンシエーションを見つけることができます。

# every object and where it's defined 
nm -A *.o | c++filt | grep ' T ' \ 
| sort -k3 > @all.definitions 

# definitions that shouldn't be duplicated: 
nm -A A-foo-int.o | c++filt | grep ' T ' \ 
| sort -k3 > @my.definitions 

# everything that shows on both lists: 
join -j3 @my.definitions @all.definitions 

編集:grepのパターンは、実際には非常にうまく機能しなかったためにsedの構文そのため、joinがあります。

関連する問題