Reputation: 1203
I have a class which needs random numbers in a couple of different member methods. I'm using C++11
and I think it is not feasible to newly create a random number generator in each method.
Is it possible to share the random number generator across the class by making it a member attribute of the class or maybe a typedef, while still ensuring good randomness?
How would I does this, maybe you can point me to a small example? Where should I set the seed for the random engine, I want to use a Mersenne twister
engine with different types of distributions (normal
& uniform
).
Upvotes: 2
Views: 2517
Reputation: 88225
Engines and distributions are values and can be members just like other objects with value types.
You should seed the engine when it is created, which means, if it's a member, when your object is constructed. My example uses an in-class initializer with random_device
to seed the engine by default. It also allows the seed to be specified, for reproducible, testable results.
I would avoid exposing too much of the implementation details, such as providing a more complete interface for interacting with the engine, as that breaks encapsulation. These should be internal, hidden details of the implementation.
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);
}
};
You need to at least store the engine between usages, because the guarantees for a random sequence to be uniformly distributed only hold for sequences of repeated calls to the same engine object. There's no guarantee about a sequence produced by using different engines.
And in fact the same is true of distributions, although I don't know of implementations where creating a new distribution each time would actually produce incorrect results (there are implementations where distributions cache values for various reasons, however, so creating distributions every time could perform worse and produce a different sequence).
For example, the common algorithm for computing a normal distribution produces two values at once. Implementations of `std::normal_distribution do this and cache the second value to use every other call. The following program exhibits this.
#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';
}
With VC++2012 I get the output:
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
Notice that the sequence produced when creating a new distribution every iteration contains only every other value of the sequence produced with a single distribution.
Upvotes: 7
Reputation: 10507
Is it possible to share the random number generator across the class by making it a member attribute of the class or maybe a typedef, while still ensuring good randomness?
Absolutely. Provided that you initialize it with a varying seed (or let it use the default), you should get "good" randomness each time you call the RNG. In fact, I would suggest that using a separate RNG for each method is not only costly, but a bad design.
As for how to implement various distributions, http://en.cppreference.com/w/cpp/numeric/random has some good examples (such as this one).
Upvotes: 1