C++11 std::generate 和 std::uniform_real_distribution 调用两次会产生

C++11 std::generate and std::uniform_real_distribution called two times gives strange results

本文关键字:std 两次 distribution generate uniform real 调用 C++11      更新时间:2023-10-16

调用 std::generate 算法从 STL 在不同的容器上两次产生等效的结果。

考虑我想用 -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);

你可以在这里测试它: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

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

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);
}

给:

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

现在使用相同的生成器使用 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();

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

任何解释?干杯

编辑:

正如dotcavader所指出的,这个问题仅仅是因为生成器对象已被复制(由std::bind和std::generate复制)。因此,生成器以完全相同的内部状态启动,这两个生成器。

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

使用 std::reference_wrapper 将引用存储在绑定中而不是副本中:

auto rand = std::bind(dis, std::ref(gen));

使用返回引用的 lambda :

auto rand = [&](){ return dis(gen); };
我相信

这里发生的事情是 std::generate 按值获取其生成器参数。一旦你把它传进去,它就会被复制。这意味着 std::generate 内部发生的情况不会影响您传入的函数对象。

因此,您再次调用 generate 并复制相同的生成器函数对象,然后该对象开始以完全相同的状态生成,包括(内部某处)用于初始化数字生成的种子。

如果你创建了一个不同的生成器函数,即使使用相同的参数,你也必须在其中获得一个不同的随机种子。这就是您的其他方法产生不同结果的方式。最终,他们的生成器从不同的种子开始。