可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
C++11 introduced the header <random>
with declarations for random number engines and random distributions. That's great - time to replace those uses of rand()
which is often problematic in various ways. However, it seems far from obvious how to replace
srand(n);
// ...
int r = rand();
Based on the declarations it seems a uniform distribution can be built something like this:
std::default_random_engine engine;
engine.seed(n);
std::uniform_int_distribution<> distribution;
auto rand = [&](){ return distribution(engine); }
This approach seems rather involved and is surely something I won't remember unlike the use of srand()
and rand()
. I'm aware of N4531 but even that still seems to be quite involved.
Is there a reasonably simple way to replace srand()
and rand()
?
回答1:
Is there a reasonably simple way to replace srand() and rand()?
Full disclosure: I don't like rand()
. It's bad, and it's very easily abused.
The C++11 random library fills in a void that has been lacking for a long, long time. The problem with high quality random libraries is that they're oftentimes hard to use. The C++11 <random>
library represents a huge step forward in this regard. A few lines of code and I have a very nice generator that behaves very nicely and that easily generates random variates from many different distributions.
Given the above, my answer to you is a bit heretical. If rand()
is good enough for your needs, use it. As bad as rand()
is (and it is bad), removing it would represent a huge break with the C language. Just make sure that the badness of rand()
truly is good enough for your needs.
C++14 didn't deprecate rand()
; it only deprecated functions in the C++ library that use rand()
. While C++17 might deprecate rand()
, it won't delete it. That means you have several more years before rand()
disappears. The odds are high that you will have retired or switched to a different language by the time the C++ committee finally does delete rand()
from the C++ standard library.
I'm creating random inputs to benchmark different implementations of std::sort() using something along the lines of std::vector<int> v(size); std::generate(v.begin(), v.end(), std::rand);
You don't need a cryptographically secure PRNG for that. You don't even need Mersenne Twister. In this particular case, rand()
probably is good enough for your needs.
Update
There is a nice simple replacement for rand()
and srand()
in the C++11 random library: std::minstd_rand
.
#include <random>
#include <iostream>
int main ()
{
std:: minstd_rand simple_rand;
// Use simple_rand.seed() instead of srand():
simple_rand.seed(42);
// Use simple_rand() instead of rand():
for (int ii = 0; ii < 10; ++ii)
{
std::cout << simple_rand() << '\n';
}
}
The function std::minstd_rand::operator()()
returns a std::uint_fast32_t
. However, the algorithm restricts the result to between 1 and 231-2, inclusive. This means the result will always convert safely to a std::int_fast32_t
(or to an int
if int
is at least 32 bits long).
回答2:
How about randutils
by Melissa O'Neill of pcg-random.org?
From the introductory blog post:
randutils::mt19937_rng rng;
std::cout << "Greetings from Office #" << rng.uniform(1,17)
<< " (where we think PI = " << rng.uniform(3.1,3.2) << ")\n\n"
<< "Our office morale is " << rng.uniform('A','D') << " grade\n";
回答3:
Assuming you want the behavior of the C-style rand
and srand
functions, including their quirkiness, but with good random, this is the closest I could get.
#include <random>
#include <cstdlib> // RAND_MAX (might be removed soon?)
#include <climits> // INT_MAX (use as replacement?)
namespace replacement
{
constexpr int rand_max {
#ifdef RAND_MAX
RAND_MAX
#else
INT_MAX
#endif
};
namespace detail
{
inline std::default_random_engine&
get_engine() noexcept
{
// Seeding with 1 is silly, but required behavior
static thread_local auto rndeng = std::default_random_engine(1);
return rndeng;
}
inline std::uniform_int_distribution<int>&
get_distribution() noexcept
{
static thread_local auto rnddst = std::uniform_int_distribution<int> {0, rand_max};
return rnddst;
}
} // namespace detail
inline int
rand() noexcept
{
return detail::get_distribution()(detail::get_engine());
}
inline void
srand(const unsigned seed) noexcept
{
detail::get_engine().seed(seed);
detail::get_distribution().reset();
}
inline void
srand()
{
std::random_device rnddev {};
srand(rnddev());
}
} // namespace replacement
The replacement::*
functions can be used exactly like their std::*
counterparts from <cstdlib>
. I have added a srand
overload that takes no arguments and seeds the engine with a “real” random number obtained from a std::random_device
. How “real” that randomness will be is of course implementation defined.
The engine and the distribution are held as thread_local
static
instances so they carry state across multiple calls but still allow different threads to observe predictable sequences. (It's also a performance gain because you don't need to re-construct the engine or use locks and potentially trash other people's cashes.)
I've used std::default_random_engine
because you did but I don't like it very much. The Mersenne Twister engines (std::mt19937
and std::mt19937_64
) produce much better “randomness” and, surprisingly, have also been observed to be faster. I don't think that any compliant program must rely on std::rand
being implemented using any specific kind of pseudo random engine. (And even if it did, implementations are free to define std::default_random_engine
to whatever they like so you'd have to use something like std::minstd_rand
to be sure.)
回答4:
Abusing the fact that engines return values directly
All engines defined in <random>
has an operator()()
that can be used to retrieve the next generated value, as well as advancing the internal state of the engine.
std::mt19937 rand (seed); // or an engine of your choosing
for (int i = 0; i < 10; ++i) {
unsigned int x = rand ();
std::cout << x << std::endl;
}
It shall however be noted that all engines return a value of some unsigned integral type, meaning that they can potentially overflow a signed integral (which will then lead to undefined-behavior).
If you are fine with using unsigned values everywhere you retrieve a new value, the above is an easy way to replace usage of std::srand
+ std::rand
.
Note: Using what has been described above might lead to some values having a higher chance of being returned than others, due to the fact that the result_type
of the engine
not having a max value that is an even multiple of the highest value that can be stored in the destination type.
If you have not worried about this in the past — when using something like rand()%low+high
— you should not worry about it now.
Note: You will need to make sure that the std::engine-type::result_type
is at least as large as your desired range of values (std::mt19937::result_type
is uint_fast32_t
).
If you only need to seed the engine once
There is no need to first default-construct a std::default_random_engine
(which is just a typedef for some engine chosen by the implementation), and later assigning a seed to it; this could be done all at once by using the appropriate constructor of the random-engine.
std::random-engine-type engine (seed);
If you however need to re-seed the engine, using std::random-engine::seed
is the way to do it.
If all else fails; create a helper-function
Even if the code you have posted looks slightly complicated, you are only meant to write it once.
If you find yourself in a situation where you are tempted to just copy+paste what you have written to several places in your code it is recommended, as always when doing copy+pasting; introduce a helper-function.
Intentionally left blank, see other posts for example implementations.
回答5:
You can create a simple function like this:
#include <random>
#include <iostream>
int modernRand(int n) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, n);
return dis(gen);
}
And later use it like this:
int myRandValue = modernRand(n);
As mentioned here