長い間、私は次のように定義されたいくつかのプログラム全体の定数を使用してコードを記述してきましたconstants.h
。
const size_t kNumUnits = 4;
const float kAlpha = 0.555;
const double kBeta = 1.2345;
このアプローチの問題点は、固定メモリ ブロックを割り当てるとき、またはループを反復するときに、この情報がコードの下位レベルで必要になることが多いため、これらのユニットでこの common を #includeconstants.h
するか、関連する値を渡す必要があることです。これらの関数が呼び出されると、実行時に変更されない値でインターフェイスが乱雑になり、コンパイラの最適化機能が損なわれます。
さらに、いくつかの定数の最上位の定義ヘッダーに依存するこのすべての下位レベルのコードを持つことは、私には悪臭のように思えます。グローバル変数に似すぎている、それらはすべて一定ですが。1 つ以上の定数を参照する必要がある各コンポーネントには共通のヘッダーを含める必要があるため、再利用可能なコードを記述することが難しくなります。このヘッダーは、一連のコンポーネントを一緒にプールするときに手動で作成および維持する必要があります。2 つ以上のコンポーネントが同じ定数を使用する場合がありますが、それぞれのプログラムで同じ値を使用する必要があるため (プログラム間で値が異なっていても)、どちらもそれ自体を定義することはできません。それがたまたま提供する他のすべての定数 - カプセル化には適していません。また、ヘッダー定義が機能する必要があるため、コンポーネントを「スタンドアロン」で使用できないことも意味します。ただし、それらが再利用可能なファイルに含まれている場合は、コンポーネントがメイン プロジェクトに取り込まれるときに手動で削除する必要があります。これにより、プログラム固有のコンポーネント ヘッダー ファイルがごちゃごちゃになり、コンポーネントが新しいプログラムで使用されるたびに手動で変更する必要があり、単純にクライアント コードから命令を取得するのではありません。
もう 1 つのオプションは、コンストラクターまたは別のメンバー関数を介して、実行時に関連する定数を提供することです。ただし、処理パフォーマンスは私にとって重要です。コンパイル時に指定された固定サイズの配列 (バッファー) をすべて操作するクラスがたくさんあります。現在、このサイズは の定数から取得されるかconstants.h
、実行時に関数パラメータとしてオブジェクトに渡されます。配列のサイズを非型のテンプレート パラメーターまたはconst
変数として指定していくつかの実験を行ってきましたが、ループのサイズはコンパイル時に固定され、より適切に最適化できるため、コンパイラーはより高速なコードを生成できるようです。次の 2 つの関数は高速です。
const size_t N = 128; // known at compile time
void foo(float * buffer) {
for (size_t i = 0; i < N; ++i) {
buffer *= 0.5f;
}
}
template <size_t N> // specified at compile time
void foo(float * buffer) {
for (size_t i = 0; i < N; ++i) {
buffer *= 0.5f;
}
}
コンパイル時に N がわからないため、あまり最適化できない純粋なランタイム バージョンとは対照的に、次のようにします。
void foo(float * buffer, size_t N) {
for (size_t i = 0; i < N; ++i) {
buffer *= 0.5f;
}
}
非型テンプレート パラメーターを使用してコンパイル時にこの情報を渡すと、グローバル定数ファイルをそのすべての定義と共に #include するのと同じパフォーマンス結果が得られますがconst
、カプセル化がはるかに良くなり、特定の情報 (およびそれ以上) を公開できます。それを必要とするコンポーネントに。
したがって、型を宣言するときに N の値を渡したいと思いますが、これはすべてのコードがテンプレート化されたコードになることを意味します (そして、コードを .hpp ファイルに移動する必要があります)。また、整数の非型パラメーターのみが許可されているように見えるため、この方法で float または double 定数を渡すことはできません。これは許可されていません:
template <size_t N, float ALPHA>
void foo(float * buffer) {
for (size_t i = 0; i < N; ++i) {
buffer[i] *= ALPHA;
}
}
だから私が持っている質問は、これを処理する最良の方法は何ですか? 結合を減らしながら、コンパイル時に指定された定数の利点を得るために、プログラム全体の定数をどのように編成する傾向がありますか?
編集:
これは、型を使用して定数値を保持しているため、テンプレート パラメーターを使用してレイヤーに渡すことができるものです。定数パラメーターはSystem
構造体で定義され、下位レイヤーに同じ名前のテンプレート パラメーターとして提供さfoo
れbar
ますSystem
。
最上位レベル:
#include "foo.h"
struct System {
typedef size_t buffer_size_t;
typedef double alpha_t;
static const buffer_size_t buffer_size = 64;
// static const alpha_t alpha = 3.1415; -- not valid C++?
static alpha_t alpha() { return 3.1415; } -- use a static function instead, hopefully inlined...
};
int main() {
float data[System::buffer_size] = { /* some data */ };
foo<System> f;
f.process(data);
}
そして foo.h の中間層:
// no need to #include anything from above
#include "bar.h"
template <typename System>
class foo {
public:
foo() : alpha_(System::alpha()), bar_() {}
typename System::alpha_t process(float * data) {
bar_.process(data);
return alpha_ * 2.0;
}
private:
const typename System::alpha_t alpha_;
bar<System> bar_;
};
次に、「下部」の bar.h で:
// no need to #include anything 'above'
template <typename System>
class bar {
public:
static const typename System::buffer_size_t buffer_size = System::buffer_size;
bar() {}
void process(float * data) {
for (typename System::buffer_size_t i = 0; i < System::buffer_size; ++i) {
data[i] *= static_cast<float>(System::alpha()); -- hopefully inlined?
}
}
};
これには、定数を参照する必要がある場合に備えて、将来のコードの多く (すべて?) を「システム」パラメーターを取るテンプレートに変換するという明確な欠点があります。また、冗長で読みにくいです。しかし、foo と bar は事前にシステム構造について何も知る必要がないため、ヘッダー ファイルへの依存は削除されます。