6

中規模または大規模で複雑なプロジェクトでは、テンプレートの宣言と定義を分離すると、コンパイル時間を短縮するのに役立ちます。

ただし、複雑なコードでは、プログラマーの小さなミスが気付かない動作の変化につながる可能性があります。たとえば、特殊化の代わりに汎用バージョンが呼び出されます。

例: 宣言の欠落により、テンプレートの特殊化が非表示になりました。

///////////////////// 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

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

質問: より堅牢なセットアップ (コンパイラーおよびプラットフォームに依存しない) は可能ですか。

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

4

4 に答える 4

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ファイルの relegate は、プライマリ テンプレートの直後に宣言されます (グッド プラクティスが推奨するように)。もしあったら、ここに来て聞く必要もないほど人気が​​あると思います。

いくつかの派手なマクロが役立つ場合もありますが、本当に複雑なプロジェクトではメンテナンスの手間よりも多くのメリットがあるとは言えません。

キーワードを導入することにより、C++03 標準でこの問題の解決が試みられましたexportが、実装の経験から、コンパイラ ベンダーのサポートが難しすぎることが判明しました。これがexport、C++ 標準の一部ではなくなった理由です (C++11 以降)。 .

モジュールのより良い解決策がC++14 に組み込まれ、テンプレート設計の解決策が提供されることを願っています。

于 2013-03-12T17:24:14.073 に答える
1

あなたができる最善のことはstatic_assert、汎用テンプレートが特殊化されるはずの型でインスタンス化されないようにすることだと思います。

次のコードは説明のみを目的としています - 私はおそらく使用しますBOOST_STATIC_ASSERT(そしてstd::is_same、c++11 を使用できる場合)。基本的な考え方は、禁止する型のセットを使用して、特殊化されていないテンプレートを暗黙的にインスタンス化することを防ぐことです。もちろん、静的アサートと特殊化を追加するのを忘れると、失敗することになります。

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;
}
于 2013-03-12T18:07:44.060 に答える
0

さて、一般的なA<T>::foo()実装をインスタンス化するコメントから、必ずしもエラーではありません。他の場所で特殊化を提供した場合にのみです。

したがって、必要なのは、コンパイラ オブジェクト ファイルの特定のリストでのみインスタンス化されるはずの特殊化と重複する名前を持つ汎用テンプレートのインスタンス化を見つけることです。これは、2 つのデータセットで一致するフィールドを探すことになります。そのために、次のものがありjoinます。

# 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 構文は、実際にはうまく機能しませんでした。

于 2013-03-13T15:13:57.203 に答える
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!"); }
于 2013-03-12T19:59:06.183 に答える