C++ 言語は、数値配列に対し長く難解な演算を実行する、科学および技術計算によく使用されます。 この言語自体は、この種の計算に必要な柔軟性と効率をすべてを備えていますが、そのコードは非常に複雑になることがあります。当然ながら、言語のオブジェクト指向機能を使用することで、この複雑さをカプセル化することができますが、数値プログラミングを簡易化するために設計されたクラスを、パフォーマンスを低下させずに正しくコーディングすることは困難です。標準 C++ ライブラリは、valarray クラステンプレートによってこれを可能にしています。
このクラステンプレートは、1 次元配列の形で必要な効率を提供します。クラス valarray を使用することで、1 次元の配列に直接的に計算を実行し、高次元の配列をわずかな労力で表示することができます。添字演算子の拡張セットは、比較的単純で低レベルの valarray から、マトリックスやその他の高度なクラスを構築するための基礎となります。
効率は、数値プログラミングにとって極めて重要な問題であるため、valarray クラスはさまざまな方法でパフォーマンスの問題に対応しています。
第 1 に valarray は、クラス内の演算を最適化するために、コンパイラの自由度を最大限にして設計しています。これは、主に valarray 内の要素のエイリアシングを防止することによって実現されます。つまり、特定の valarray 内の要素はどれも、通常の配列の場合とまったく同様に、固有の一意のメモリーアドレスに配置されます。これによって、valarray の実装は、デフォルト値を単一の記憶値とする全要素のエイリアシングなど、ある種の最適化を使用できなくなりますが、この損失は、配列の演算を簡易化する有能なオプティマイザの能力によって十分補填されます。
第 2 に、valarray クラスは内部最適化テンプレートを使用して、できる限り効率的なデータコピー方法が使用されることを保証します。この最適化の一部は、エイリアシングの制限によって可能です。
最後に、valarray クラスは、使用される型に一定の条件を課します。これらの制限の大部分はパフォーマンスに無関係で、汎用数値演算を可能にする上で必要とされるものです。ただし、特に初期化の意味から、部分的に最適化を可能にするための制限が少なくとも部分的に 1 つは存在します。これらの制限を以下にまとめます。
valarray は、次の要求に一致する型 T のみにインスタンス化することができます。
型 T は抽象クラスであってはなりません。
型 T は参照型であってはなりません。つまり、valarray<int&> は許可されません。
型 T は const-volatile に認定されていません。つまり、
valarray<const int> は許可されません。
型 T は単項 operator& を多重定義してはなりません。
型 T は例外を送出してはなりません。
型 T がクラスの場合、公開デフォルトコンストラクタ、識別形式 T::T(const T&) を持つ公開コピーコンストラクタ、公開デストラクタが必要です。
型 T がクラスの場合、T& T::operator=(const T&) または T& T::operator=(T) のいずれかに一致する識別形式を持つ公開代入演算子が必要です。
型 T がクラスの場合、そのデフォルトコンストラクタ、コピーコンストラクタ、および代入演算子は、デフォルトの作成後の代入とコピー作成が等しくなるように動作しなければなりません。また、オブジェクトの破壊後のコピー作成は、代入と等しくなければなりません。
コピーの作成と代入の間の必然的な関係を以下にまとめます。ここで T は型を、t と v は型のインスタンスを、p は型のインスタンスを指すポインタを表します。
T t(), t = v |
は右と同じ意味である: |
T t(v) |
new (p) T(), *p = v |
は右と同じ意味である: |
new (p) T(v) |
p->~T(), new (p) T(v) |
は右と同じ意味である: |
*p = v |
この要約は、コピー作成や代入のプロセスには、技巧的な点がないことを証明しています。valarray の実装は、これらの演算が上記の等価性を持つことを期待することがあるため (実装によっては、これに注意しないこともあります)、この整合性は移植性にとって特に重要です。
int、long、float などの組み込み数値型はすべて、valarray の型要求を明確に満たしています。以下に、条件を満たす最小限クラスの例を挙げます。このクラスは、実際にコンパイラの生成するコンストラクタ、デストラクタ、代入演算子で十分なごく単純なものです。このように、特殊なものはまったく必要ありません。むしろ要求は、特殊性がないという点に従っています。
class Num { int val_; public: Num() : val_(0) // 公開デフォルトコンストラクタ {} Num(const Num& n) : val_(n.val()) // 公開コピーコンストラクタ {} ~Num() // 公開デストラクタ {} Num& operator=(const Num& n) // 公開代入演算子 { val-_ = n.val(); return *this; } int val() const { return val_; } int val(int v) { int tmp = val_; val_ = v; return tmp; } };
ここで、クラス valarray の型要求に一致しない Num クラスの簡単なバリエーションを検討してみましょう。このクラスの用途次第では valarray で問題とならないこともありますが、デフォルトの作成の後の代入はコピー作成とは異なる状態となるため、これは明らかに要求に違反します。
class Num { bool assigned_; int val_; public: Num() : val_(0) , assigned_(false) // 公開デフォルトコンストラクタ {} Num(const Num& n) // 公開コピーコンストラクタ : val_(n.val()), assigned_(n.assigned()) {} ~Num() // 公開デストラクタ {} Num& operator=(const Num& n) // 公開代入演算子 { val_ = n.val(); assigned_ = true; // n.assigned() == false の場合 // 規則 6 に違反する return *this; } int val() const { return val_; } int val(int v) { int tmp = val_; val_ = v; return tmp; } bool assigned() const { return assigned; } };
特定の型に対して定義されていない演算は、その型の valarray には使用できないことに注意してください。たとえば、標準 complex クラステンプレートの場合と同様に、型が順序付き演算を持たない場合、これらの順序付きオペランドはその型の valarray には使用できません。
もう 1 つの重要な機能は、valarray クラスと共に、valarray ヘッダーが複数の補助クラスを定義して、拡張部分集合演算をサポートすることです。これらの補助クラスは slice、gslice、slice_array、gslice_array、indirect_array、mask_array です。これらについては、22.4.2 節以降で説明します。ここでは、slice および gslice が、 Basic Linear Algreba Subprograms (BLAS)などのスライスや生成されたスライスのパラメータを配列で定義することを指摘するにとどめます。残りの補助クラスは、部分集合演算によって返される型を定義します。これらの補助クラスは、公開コンストラクタを持たないため、プログラムによって直接インスタンス化することはできません。
valarray を使用するプログラムは、valarray ヘッダーファイルをインクルードする必要があります。
#include <valarray>