web-dev-qa-db-ja.com

C ++テンプレートを使用してコンパイル時間を短縮する方法

C++アプリの一部を古いC型配列の使用からテンプレート化されたC++コンテナークラスに変更しているところです。詳細については、 この質問 を参照してください。ソリューションは非常にうまく機能していますが、テンプレート化されたコードに小さな変更を加えるたびに、非常に大量の再コンパイルが行われるため、ビルド時間が大幅に遅くなります。テンプレートコードをヘッダーから取得してcppファイルに戻す方法はありますか?それにより、実装を少し変更しても大規模な再構築が発生しませんか?

34
SmacL

一般的なルールが適用されると思います。コードの各部分間の結合を減らすようにしてください。大きすぎるテンプレートヘッダーを一緒に使用される関数の小さなグループに分割するため、すべてのソースファイルにすべてを含める必要はありません。

また、ヘッダーを安定した状態にすばやく移行するようにしてください。おそらく、小さなテストプログラムに対してヘッダーをテストして、大きなプログラムに統合するときにヘッダーを(あまり)変更する必要がないようにします。

(他の最適化と同様に、最初に作業負荷を大幅に削減する「アルゴリズム」最適化を見つけるよりも、テンプレートを処理するときにコンパイラーの速度を最適化する方が価値がない場合があります。)

19
UncleBens

いくつかのアプローチ:

  • exportキーワード は理論的には役立つ可能性がありますが、サポートが不十分であり、C++ 11で正式に削除されました。
  • 明示的なテンプレートのインスタンス化( ここ または ここ を参照)は、必要なインスタンス化を事前に予測できる場合(および気にしない場合)、最も簡単なアプローチです。このリストを維持する)。
  • 外部テンプレート 、これはすでにいくつかのコンパイラで拡張機能としてサポートされています。 externテンプレートでは、必ずしもテンプレート定義をヘッダーファイルから移動できるとは限りませんが、コンパイルとリンクが高速になります(テンプレートコードをインスタンス化してリンクする必要がある回数が減ります)。
  • テンプレートのデザインによっては、その複雑さのほとんどを.cppファイルに移動できる場合があります。標準的な例は、void*のタイプセーフでないベクターを単にラップするタイプセーフなベクターテンプレートクラスです。複雑さはすべて、.cppファイルにあるvoid*ベクトルにあります。スコットマイヤーズは、でより詳細な例を示しています 効果的なC++ (第2版の項目42「私的継承を慎重に使用する」)。
26
Josh Kelley
  • export キーワードをサポートするコンパイラを入手することもできますが、それが続く可能性はほとんどありません。

  • 明示的なインスタンス化 を使用できますが、残念ながら、使用するテンプレートの種類を事前に予測する必要があります。

  • アルゴリズムからテンプレート化された型を除外できる場合は、それを独自の.ccファイルに入れることができます。

  • 大きな問題でない限り、これはお勧めしませんが、void*実装の呼び出しで実装されるテンプレートコンテナインターフェイスを提供して、自由に変更できる場合があります。

6
Stephen

まず、完全を期すために、簡単な解決策について説明します。必要な場合にのみテンプレート化されたコードを使用し、テンプレート以外のコードに基づいて(独自のソースファイルに実装します)。

ただし、本当の問題は、一般的なオブジェクト指向プログラミングを使用するのと同じようにジェネリックプログラミングを使用し、最終的にクラスが肥大化することだと思います。

例を見てみましょう:

// "bigArray/bigArray.hpp"

template <class T, class Allocator>
class BigArray
{
public:
  size_t size() const;

  T& operator[](size_t index);
  T const& operator[](size_t index) const;

  T& at(size_t index);
  T const& at(size_t index);

private:
  // impl
};

これはあなたに衝撃を与えますか?おそらくそうではありません。結局のところ、それはかなりミニマリストのようです。問題は、そうではないということです。 atメソッドは、一般性を失うことなく除外できます。

// "bigArray/at.hpp"

template <class Container>
typename Container::reference_type at(Container& container,
                                      typename Container::size_type index)
{
  if (index >= container.size()) throw std::out_of_range();
  return container[index];
}

template <class Container>
typename Container::const_reference_type at(Container const& container,
                                            typename Container::size_type index)
{
  if (index >= container.size()) throw std::out_of_range();
  return container[index];
}

さて、これは呼び出しをわずかに変更します:

// From
myArray.at(i).method();

// To
at(myArray,i).method();

ただし、Koenigのルックアップのおかげで、同じ名前空間に配置されている限り、それらを非修飾と呼ぶことができるので、それは習慣の問題です。

例は考案されていますが、一般的なポイントは立っています。その汎用性のために、at.hppbigArray.hppを含める必要はなく、メンバーメソッドであるかのようにタイトなコードを生成することに注意してください。必要に応じて、他のコンテナーで呼び出すことができます。

そして今、BigArrayのユーザーはそれを使用しない場合はat.hppを含める必要はありません...したがって、依存関係を減らし、そのファイルのコードを変更しても影響を受けません:たとえばstd::out_of_range呼び出しを変更して、ファイル名と行番号、コンテナーのアドレス、そのサイズ、およびアクセスしようとしたインデックスを指定します。

もう1つの(それほど明白ではない)利点は、BigArrayの整合性制約に違反した場合、atはクラスの内部を混乱させることができないため、明らかに原因外であるため、容疑者の数。

これは、 C++ Coding Standards :のハーブサッターなど、多くの作成者によって推奨されています。

項目44:非メンバーの非友達関数を書くことを好む

Boost...で広く使用されていますが、コーディングの習慣を変える必要があります。

そしてもちろん、あなたはあなたが依存しているものだけを含める必要があります、これを理解するのを助けることができる含まれているが未使用のヘッダーファイルを報告する静的C++コードアナライザーがあるべきです。

5
Matthieu M.

テンプレートなしで基本クラスを定義し、実装のほとんどをそこに移動できます。テンプレート化された配列は、すべてに基本クラスを使用するプロキシメソッドのみを定義します。

3
AareP