让我们比较一下你的两个案例之间的差异,看看为什么会发生这种情况。
案例 1:
int main() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0, 1);
for(int i = 0; i < 10; i++) {
std::cout << dis(gen) << std::endl;
}return 0;
}
在您的第一种情况下,程序执行 main 函数,这里发生的第一件事是您在属于 main() 的堆栈上创建 std::random_device、std::mt19337 和 std::uniform_real_distribution<> 的实例的范围。您的 mersenne twister gen 会使用随机设备 rd 的结果初始化一次。您还初始化了您的分布dis,使其具有从0 到1 的值范围。每次运行您的应用程序时,这些仅存在一次。
现在您创建一个从索引0 开始并递增到9 的for 循环,并且在每次迭代中,您通过使用分布dis 传递给的operator()() 将结果值显示到cout它是你已经播种的一代gen。每次在这个循环上dis(gen) 都会产生不同的值,因为gen 已经只播种了一次。
案例 2:
double generateRandomNumber() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0, 1);
return dis(gen);
}
int main() {
for(int i = 0; i < 10; i++) {
std::cout << generateRandomNumber() << std::endl;
}return 0;
}
在这个版本的代码中,让我们看看哪些相似,哪些不同。这里程序执行并进入main()函数。这次它遇到的第一件事是从0 到9 的for 循环,类似于上面的main,但是这个循环是main 堆栈上的第一件事。然后调用cout 以显示来自名为generateRandomNumber() 的user defined function 的结果。这个函数总共被调用了10 次,每次你遍历 for 循环时,这个函数都有自己的堆栈内存,将被缠绕和解开或创建和销毁。
现在让我们跳到这个名为generateRandomNumber() 的user defined function。
代码看起来几乎与之前直接在main() 中的代码完全相同,但这些变量位于generateRandomNumber() 的堆栈中,并且具有其作用域的生命周期。每次这个函数进入和离开范围时,这些变量都会被创建和销毁。这里的另一个区别是这个函数也返回dis(gen)。
注意:我不确定100% 是否会返回copy,或者编译器是否最终会进行某种优化,但按值返回通常会产生副本。
最后,当 then 函数 generateRandomNumber() 返回时,就在它完全超出调用 std::uniform_real_distribrution<> 的 operator()() 的范围之前,它进入自己的堆栈和范围,然后返回到主 generateRandomNumber()这么简单,然后回到主要的。
-可视化差异-
正如您所见,这两个程序完全不同,确切地说是非常不同。如果您想更直观地证明它们不同,您可以使用任何可用的在线编译器将每个程序输入到它在assembly 中向您显示该程序的位置,然后比较两个程序集版本以查看它们的最终差异。
另一种可视化这两个程序之间差异的方法不仅是查看它们的 assembly 等效项,而且还可以使用 debugger 逐行遍历每个程序,并密切关注 stack calls 和缠绕和展开它们,并在它们被初始化、返回和销毁时密切关注所有值。
-评估和推理-
第一个按预期工作的原因是因为您的random device、您的generator 和您的distribution 都具有main 的生命周期,并且您的generator 仅使用您的随机设备播种一次,并且您每次在 for 循环中只有一个发行版。
在您的第二个版本中,main 对此一无所知,它所知道的只是它正在经历一个 for 循环并将返回的数据从用户函数发送到 cout。现在,每次它通过 for 循环时,都会调用这个函数,并且正如我所说的那样,它的堆栈每次都会被创建和销毁,所以如果它的变量正在被创建和销毁。因此,在这种情况下,您正在创建和销毁 10: rd、gen(rd()) 和 dis(0,1)s 实例。
-结论-
这比我上面描述的要多,与你的随机数生成器的行为有关的另一部分是用户Kane 在他对你的问题的评论中对你的陈述中提到的:
来自en.cppreference.com/w/cpp/numeric/random/random_device:
“std::random_device 可以根据
实现定义的伪随机数引擎 [...]。
在这种情况下,每个 std::random_device 对象都可能生成
相同的数列。”
每次创建和销毁时,您都会一遍又一遍地用新的random_device 播种generator,但是如果您的特定机器或操作系统不支持使用random_device,它最终可能会使用一些任意值作为其种子值,或者最终可能使用系统时钟来生成种子值。
假设它最终使用了系统时钟,main() 的 for 循环的执行发生得如此之快,以至于 10 对 generateRandomNumber() 的调用所做的所有工作都已经执行在几毫秒过去之前。因此,这里的增量时间非常小并且可以忽略不计,它在每次通过时生成相同的种子值,并且从分布中生成相同的值。