web-dev-qa-db-ja.com

テンプレート:前方宣言を使用してコンパイル時間を短縮しますか?

多くのテンプレート化されたクラスで構成されるライブラリを処理する必要があります。もちろん、これらはすべてヘッダーファイルに実装されています。今、私は、ライブラリ全体をコンパイルユニットのそれぞれに含める必要があるという事実から生じる、耐えられないほど長いコンパイル時間を短縮する方法を見つけようとしています。

テンプレートにもかかわらず、前方宣言を使用する可能性はありますか?以下の例に沿って何かを試みています。例として、#include <vector>を回避しようとしましたが、Push_backが未定義であるため、リンカーエラーが発生します。

#include <iostream>

namespace std {
  template<class T>
  class vector {
  public:
    void Push_back(const T& t);
  };
}

int main(int argc, char** argv) {
  std::vector<int>* vec = new std::vector<int>();
  vec->Push_back(3);
  delete vec;
  return EXIT_SUCCESS;
}

$ g++ fwddecl.cpp
ccuqbCmp.o(.text+0x140): In function `main':
: undefined reference to `std::vector<int>::Push_back(int const&)'
collect2: ld returned 1 exit status

プリコンパイル済みヘッダーを1回試しましたが、コンパイル時間はまったく変わりませんでした(実際のヘッダーではなく、実際にロードされていることを確認しました)。しかし、プリコンパイル済みヘッダーが進むべき道であると皆さんが言うなら、私はそれをもう一度試してみます。

PDATE: STLクラスを前方宣言する価値はないと言う人もいます。上記のSTLvectorは単なる例であることを強調する必要があります。私は実際にはSTLクラスを前方宣言しようとはしていませんが、それは私が使用しなければならないいくつかのライブラリの他の、高度にテンプレート化されたクラスに関するものです。

PDATE 2:上記の例を実際に正しくコンパイルしてリンクする方法はありますか? Loganは、-fno-implicit-templatesを使用し、template class std::vector<int>をどこかに置くことを提案しています。おそらく.cppでコンパイルされる別の-fno-implicit-templatesファイルに入れますが、それでもリンカーエラーが発生します。繰り返しになりますが、実際に使用しているテンプレート化されたクラスに適用できるように、std::vectorでどのように機能するかを理解しようとしています。

26
Frank

そのようなクラスの「部分」を前方宣言することはできません。可能であっても、コードをどこかにインスタンス化して、リンクできるようにする必要があります。それを処理する方法はいくつかあります。一般的なコンテナ(ベクトルなど)のインスタンス化を使用して小さなライブラリを作成し、それらをリンクすることができます。そうすれば、コンパイルするだけで済みます。 vector <int>を1回。これを実装するには、少なくともg ++を使用していると仮定して、-fno-implicit-templatesのようなものを使用し、template class std::vector<int>を使用してライブラリ内のテンプレートを明示的にインスタンス化する必要があります。


だから、実際の実用的な例。ここにa.cppとb.cppの2つのファイルがあります

a.cpp:

#include <vector> // still need to know the interface
#include <cstdlib>

int main(int argc, char **argv) {
  std::vector<int>* vec = new std::vector<int>();
  vec->Push_back(3);
  delete vec;
  return EXIT_SUCCESS;
}

これで、-fno-implicit-templatesを使用してa.cppをコンパイルできます。

g++ -fno-implicit-templates -c a.cpp

これは私にa.oを与えるでしょう。次に、リンクしようとすると、次のようになります。

g++ a.o
/usr/bin/ld: Undefined symbols:
std::vector<int, std::allocator<int> >::_M_insert_aux(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&)
void std::_Destroy<int*, std::allocator<int> >(int*, int*, std::allocator<int>)
collect2: ld returned 1 exit status

ダメ。そこで、b.cppに目を向けます。

#include <vector>
template class std::vector<int>;
template void std::_Destroy(int*,int*, std::allocator<int>);
template void std::__uninitialized_fill_n_a(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, unsigned long, int const&, std::allocator<int>);
template void std::__uninitialized_fill_n_a(int*, unsigned long, int const&, std::allocator<int>);
template void std::fill(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&);
template __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > > std::fill_n(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, unsigned long, int const&);
template int* std::fill_n(int*, unsigned long, int const&);
template void std::_Destroy(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, std::allocator<int>);

今、あなたはあなた自身に言っています、これらすべての余分なテンプレートのものはどこから来たのですか? template class std::vector<int>が表示されますが、それで問題ありませんが、残りはどうでしょうか。簡単に言えば、これらの実装は必然的に少し厄介であり、手動でインスタンス化すると、ひいてはこの厄介さの一部が漏れ出します。インスタンス化するために何が必要かをどうやって理解したのか疑問に思われるかもしれません。さて私はリンカーエラーを使用しました;)。

だから今私たちはb.cppをコンパイルします

g++ -fno-implicit-templates -c b.cpp

そして、私たちは体臭を取得します。 a.oとb.oをリンクすると

g++ a.o b.o

やあ、リンカーエラーはありません。

したがって、更新された質問について詳細を説明するために、これが自家製のクラスである場合、必ずしもこれほど厄介である必要はありません。たとえば、インターフェースを実装から分離することができます。 a.cppとb.cppに加えて、c.h、c.cppがあるとしましょう

c.h

template<typename T>
class MyExample {
  T m_t;
  MyExample(const T& t);
  T get();
  void set(const T& t);
};

c.cpp

template<typename T>
MyExample<T>::MyExample(const T& t) : m_t(t) {}
template<typename T>
T MyExample<T>::get() { return m_t; }
template<typename T>
void MyExample<T>::set(const T& t) { m_t = t; }

a.cpp

 #include "c.h" // only need interface
 #include <iostream>
 int main() {
   MyExample<int> x(10);
   std::cout << x.get() << std::endl;
   x.set( 9 );
   std::cout << x.get() << std::endl;
   return EXIT_SUCCESS;
 }

b.cpp、「ライブラリ」:

 #include "c.h" // need interface
 #include "c.cpp" // need implementation to actually instantiate it
 template class MyExample<int>;

ここで、b.cppをb.oに一度コンパイルします。 a.cppが変更された場合は、それを再コンパイルしてb.oにリンクする必要があります。

35
Logan Capaldo

前方宣言により、これを行うことができます。

template <class T> class vector;

次に、ベクトルを定義せずに(vectorのヘッダーファイルを含めずに)vector<whatever>への参照とポインターを宣言できます。これは、通常の(非テンプレート)クラスの前方宣言と同じように機能します。

特にテンプレートの問題は、通常、クラス宣言だけでなく、ヘッダーファイル内のすべてのメソッド定義も必要になることです(コンパイラが必要なテンプレートをインスタンス化できるようにするため)。明示的なテンプレートのインスタンス化(-fno-implicit-templatesで強制的に使用できます)は、この回避策です。メソッド定義をソースファイルに入れて(または、 Googleスタイルガイド の例に従って、インクルードする必要のない-inl.hヘッダーファイルに入れて)、明示的にインスタンス化できます。彼らはこのように:

template <class int> class vector;

これを利用するために実際には-fno-implicit-templatesは必要ないことに注意してください。コンパイラは、リンカが後でそれを理解することを前提として、定義のないテンプレートのインスタンス化を黙って回避します。また、-fno-implicit-templatesを追加すると、allテンプレートの使用が難しくなるため(時間のかかるテンプレートだけでなく)、お勧めしません。

サンプルコードの問題は、真のstd::vectorクラスを前方宣言していないことです。 <vector>を含めないことで、独自の非標準のvectorクラスを作成し、Push_backを定義することはないため、コンパイラーがインスタンス化するものは何もありません。

私はプリコンパイル済みヘッダーを使用して大きな効果を上げました。なぜ彼らがあなたを助けなかったのか分かりません。変更されないすべてのヘッダーを単一のall.hに配置し、プリコンパイルして、straceなどで検証し、all.h.pchが読み込まれ、個々のヘッダーファイルが読み込まれなかったことを確認しましたか? (straceの使用方法:g++ mytest.ccを実行する代わりに、strace -o strace.out g++ mytest.ccを実行し、テキストエディタでstrace.outを表示し、open(呼び出しを検索してどれを確認するかファイルが読み取られています。)

22
Josh Kelley

前方宣言では、メンバーまたはパラメーターをその型へのポインターまたは参照としてのみ宣言できます。上記のタイプの内部を必要とするメソッドまたはその他のものを使用することはできません。とはいえ、コンパイル時間を短縮しようとすると、前方宣言が本当に制限されることがわかりました。プリコンパイル済みヘッダーがコンパイル時間に本当に役立つことがわかったので、プリコンパイル済みヘッダーの可能性をもう少し調査することをお勧めしますが、それはg ++ではなくWindowsでVisualC++を使用した場合でした。

6
gix

有る <iosfwd>これにより、iostreamクラスの前方宣言が得られますが、一般に、stlテンプレートについて、前方宣言に関してできることはあまりありません。

プリコンパイル済みヘッダーが最適です。速度の向上に気付くことはありません。最初にコンパイルするときに速度が上がりますが、プリコンパイル済みヘッダー(またはそれに含まれるもの)を変更するたびに、その価格を1回だけ支払う必要があります。

この質問を参照 コンパイルの高速化に関するその他のアイデア。

3
Eclipse