C++ アプリの一部を古い C 型配列の使用からテンプレート化された C++ コンテナー クラスに変更中です。詳細については、この質問を参照してください。ソリューションは非常にうまく機能していますが、テンプレート化されたコードに小さな変更を加えるたびに、非常に大量の再コンパイルが発生するため、ビルド時間が大幅に遅くなります。テンプレート コードをヘッダーから取り出して cpp ファイルに戻す方法はありますか?これにより、マイナーな実装の変更によって大幅な再構築が発生することはありません。
6 に答える
いくつかのアプローチ:
- export キーワードは理論的には役に立ちますが、サポートが不十分であり、C++11 で正式に削除されました。
- 明示的なテンプレートのインスタンス化 (こちらまたはこちらを参照) は、必要なインスタンス化を事前に予測できる場合 (およびこのリストを維持することを気にしない場合) は、最も簡単なアプローチです。
- Extern テンプレート。拡張機能として複数のコンパイラで既にサポートされています。extern テンプレートでは必ずしもテンプレート定義をヘッダー ファイルから移動できるわけではありませんが、(テンプレート コードをインスタンス化してリンクする必要がある回数を減らすことで) コンパイルとリンクを高速化することは理解しています)。
- テンプレートのデザインによっては、その複雑さのほとんどを .cpp ファイルに移動できる場合があります。標準的な例は、タイプ セーフでないベクトルをラップするだけのタイプ セーフなベクトル テンプレート クラスです
void*
。すべての複雑さはvoid*
、.cpp ファイルに存在するベクトルにあります。Scott Meyers は、 Effective C++でより詳細な例を示しています(第 2 版の項目 42、「慎重にプライベート継承を使用する」)。
一般的なルールが適用されると思います。コードの部分間の結合を減らすようにしてください。大きすぎるテンプレート ヘッダーを一緒に使用する関数の小さなグループに分割して、すべてのソース ファイルにすべてを含める必要がないようにします。
また、おそらく小さなテスト プログラムに対してヘッダーをテストして、ヘッダーをすばやく安定した状態にするようにしてください。そうすれば、大きなプログラムに統合されたときに (あまり) 変更する必要がなくなります。
(他の最適化と同様に、テンプレートを処理するときにコンパイラの速度を最適化することは、最初からワークロードを大幅に削減する「アルゴリズム的」最適化を見つけるよりも価値が低い場合があります。)
まず第一に、完全を期すために、簡単な解決策について説明します。必要な場合にのみテンプレート化されたコードを使用し、テンプレート化されていないコードに基づいています (独自のソース ファイルに実装されています)。
ただし、本当の問題は、典型的な OO プログラミングを使用して肥大化したクラスになるように、ジェネリック プログラミングを使用することだと思います。
例を見てみましょう:
// "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.hpp
その汎用性により、含める必要がなく、メンバ メソッドであるかのように厳密なコードが生成されることに注意してくださいbigArray.hpp
。必要に応じて、他のコンテナで呼び出すことができます。
そして今、ユーザーはそれを使用しない場合BigArray
は含める必要はありませat.hpp
ん...したがって、依存関係を減らし、そのファイルのコードを変更しても影響を受けません。たとえばstd::out_of_range
、ファイル名と行番号をフィーチャーする呼び出しを変更します。コンテナーのアドレス、そのサイズ、およびアクセスしようとしたインデックス。
もう 1 つの (あまり明白ではない) 利点は、整合性制約にBigArray
違反した場合at
、クラスの内部をいじることができないため、明らかに原因外であり、容疑者の数が減ることです。
これは Herb Sutters in C++ Coding Standardsなど、多くの著者によって推奨されています。
項目44: 非メンバ非フレンド関数を書くことを好む
Boostで広く使用されています...しかし、コーディングの習慣を変える必要があります!
もちろん、依存しているものだけを含める必要があります。これを理解するのに役立つ、含まれているが未使用のヘッダーファイルを報告する静的 C++ コードアナライザーが必要です。
exportキーワードをサポートするコンパイラを入手できますが、それが続く可能性はほとんどありません。
明示的なインスタンス化を使用できますが、残念ながら、使用するテンプレートの種類を事前に予測する必要があります。
テンプレート化された型をアルゴリズムから除外できる場合は、それを独自の .cc ファイルに入れることができます。
void*
重大な問題でない限り、これはお勧めしませんが、自由に変更できる実装への呼び出しで実装されるテンプレート コンテナー インターフェイスを提供できる場合があります。
テンプレートなしで基本クラスを定義し、ほとんどの実装をそこに移動できます。テンプレート化された配列は、すべてに基本クラスを使用するプロキシ メソッドのみを定義します。