调用两次的C++ 11 std :: generate和std :: uniform_real_distribution给出了奇怪的结果

Plu*_*luc 5 c++ random stl c++11

在不同容器上从STL调用std :: generate算法两次会产生相同的结果.

考虑我想要填充两个浮点数组,其中随机数介于-1之间.和:1:

std::array<float, 1000> x;
std::array<float, 1000> y;

std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_real_distribution<float> dis(-1.f, 1.f);
auto rand = std::bind(dis, gen);
std::generate(x.begin(), x.end(), rand);
std::generate(y.begin(), y.end(), rand);
Run Code Online (Sandbox Code Playgroud)

你可以在这里测试一下:http://ideone.com/X712IU.两个数组都填充了完全相同的值:

0:  -0.411968,  -0.411968
1:    0.55158,    0.55158
2:    0.69889,    0.69889
3:  -0.901328,  -0.901328
4:  -0.556142,  -0.556142
5:  -0.798431,  -0.798431
6:  -0.570874,  -0.570874
7:   0.928999,   0.928999
8:   0.118056,   0.118056
9:  -0.655123,  -0.655123
Run Code Online (Sandbox Code Playgroud)

现在,如果我在生成之间创建一个新生成器,它可以正常工作:

std::array<float, 1000> x;
std::array<float, 1000> y;

// Generators in different scopes, OK
std::random_device rd;
{
  std::mt19937_64 gen(rd());
  std::uniform_real_distribution<float> dis(-1.f, 1.f);
  auto rand = std::bind(dis, gen);
  std::generate(x.begin(), x.end(), rand);
}
{
  std::mt19937_64 gen(rd());
  std::uniform_real_distribution<float> dis(-1.f, 1.f);
  auto rand = std::bind(dis, gen);
  std::generate(y.begin(), y.end(), rand);
}
Run Code Online (Sandbox Code Playgroud)

给:

0:   0.391496,   -0.64993
1:   0.429592,   0.835015
2: 0.00735116,    0.77657
3:  -0.548355, -0.0794801
4:  -0.312095,  -0.119841
5:   0.931296,   0.997449
6:  -0.934924,  -0.832223
7:   0.432267,   0.181224
8:   0.942709,   0.165024
9:   0.315852,  -0.654576
Run Code Online (Sandbox Code Playgroud)

现在使用for循环的相同生成器,它也可以工作:

// Both arrays assigned in the same loop, OK
for(size_t i = 0; i < x.size(); ++i)
{
  x[i] = rand();
  y[i] = rand();
}

// Arrays in separated loops, OK
for(size_t i = 0; i < x.size(); ++i)
  x[i] = rand();

for(size_t i = 0; i < y.size(); ++i)
  y[i] = rand();
Run Code Online (Sandbox Code Playgroud)

它看起来像是与std :: generate相关的东西,但我找不到会导致这种行为的原因.

任何解释?干杯

编辑:

正如dotcavader所指出的那样,问题只是因为生成器对象已被复制(由std :: bind和std :: generate).因此,对于两个生成,生成器以完全相同的内部状态开始.

为了完成答案,Praetorian和Casey给出了两个简单的解决方案:

使用std :: reference_wrapper在bind中存储引用而不是副本:

auto rand = std::bind(dis, std::ref(gen));
Run Code Online (Sandbox Code Playgroud)

使用返回引用的lambda:

auto rand = [&](){ return dis(gen); };
Run Code Online (Sandbox Code Playgroud)

pol*_*ver 6

我相信这里发生的是std :: generate按值获取其生成器参数.一旦你传入它,它就被复制了.这意味着std :: generate中发生的事情不会影响您传入的函数对象.

所以你再次调用generate,然后你复制相同的生成器函数对象,然后开始生成完全相同的状态,包括(在那里的某个地方)哪个种子用于初始化数字生成.

如果创建不同的生成器函数,即使使用相同的参数,也必须在其中获取不同的随机种子.这就是您的其他方法产生不同结果的方式.最终,他们的发电机以不同的种子开始.

  • +1以避免这种情况你可以传递包含在`std :: reference_wrapper`中的生成器对象 (3认同)
  • @ThePluc实际上有4个`mt19937_64`:`gen`的实例,`rand`里面的副本(来自`std :: bind`的结果),副本传递给第一个`std :: generate`并将副本传递给第二个`std :: generate`.最简单的修复可能是通过引用在lambda` [&](){return dis(gen);中捕获原始生成器.只需让lambda自由复制即可. (3认同)