エンジンとディストリビューションは値であり、値型を持つ他のオブジェクトと同様にメンバーにすることができます。
エンジンの作成時、つまりエンジンがメンバーの場合は、オブジェクトの構築時にエンジンをシードする必要があります。私の例では、クラス内イニシャライザを使用して、random_device
デフォルトでエンジンをシードしています。また、再現可能でテスト可能な結果を得るために、シードを指定することもできます。
エンジンと対話するためのより完全なインターフェイスを提供するなど、実装の詳細を公開しすぎることは避けます。カプセル化が壊れるためです。これらは、実装の内部の隠された詳細である必要があります。
std::mt19937 make_seeded_engine() {
std::random_device r;
std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
return std::mt19937(seed);
}
struct Foo {
std::mt19937 eng = make_seeded_engine();
std::uniform_int_distribution<> dist1 {1, 20};
std::uniform_real_distribution<> dist2 {0.0, 100.0};
Foo() = default;
template<typename SeedSeq>
Foo(SeedSeq &&seed) : eng(seed) {}
int bar() {
return dist1(eng);
}
double baz() {
return dist2(eng);
}
};
ランダムなシーケンスが均一に分散されるという保証は、同じエンジン オブジェクトへの繰り返しの呼び出しのシーケンスに対してのみ保持されるため、少なくとも使用間でエンジンを保存する必要があります。異なるエンジンを使用して生成されたシーケンスについての保証はありません。
実際、同じことがディストリビューションにも当てはまりますが、毎回新しいディストリビューションを作成すると実際に誤った結果が生成される実装については知りません (ただし、さまざまな理由でディストリビューションが値をキャッシュする実装があるため、毎回ディストリビューションを作成すると、パフォーマンスが低下し、別のシーケンスが生成されます)。
たとえば、正規分布を計算するための一般的なアルゴリズムは、一度に 2 つの値を生成します。`std::normal_distribution の実装はこれを行い、他のすべての呼び出しを使用するために 2 番目の値をキャッシュします。次のプログラムはこれを示しています。
#include <iostream>
#include <random>
int main() {
typedef std::mt19937 Engine;
typedef std::normal_distribution<> Distribution;
Engine eng(1);
Distribution dist;
for (int i=0; i<10; ++i)
std::cout << dist(eng) << ' ';
std::cout << '\n';
eng.seed(1);
for (int i=0; i<10; ++i)
std::cout << Distribution()(eng) << ' ';
std::cout << '\n';
}
VC++2012 を使用すると、次の出力が得られます。
0.156066 0.3064 -0.56804 -0.424386 -0.806289 -0.204547 -1.20004 -0.428738 -1.18775 1.30547
0.156066 -0.56804 -0.806289 -1.20004 -1.18775 -0.153466 0.133857 -0.753186 1.9671 -1.39981
反復ごとに新しい分布を作成するときに生成されるシーケンスには、単一の分布で生成されたシーケンスの他のすべての値のみが含まれることに注意してください。