Случайный порядок чисел в С++ с использованием <random>
У меня есть следующий код, который я написал для проверки части более крупной программы:
#include <fstream>
#include <random>
#include <iostream>
using namespace std ;
int main()
{
mt19937_64 Generator(12187) ;
mt19937_64 Generator2(12187) ;
uniform_int_distribution<int> D1(1,6) ;
cout << D1(Generator) << " " ;
cout << D1(Generator) << " " << D1(Generator) << endl ;
cout << D1(Generator2) << " " << D1(Generator2) << " " << D1(Generator2) << endl ;
ofstream g1("g1.dat") ;
g1 << Generator ;
g1.close() ;
ofstream g2("g2.dat") ;
g2 << Generator2 ;
g2.close() ;
}
Два генератора засеяны одним и тем же значением, поэтому я ожидал, что вторая строка на выходе будет идентична первой. Вместо этого вывод
1 1 3
1 3 1
Состояние двух генераторов, напечатанных в файлах *.dat
, одинаковое. Мне было интересно, может ли быть какая-то скрытая многопоточность в генерации случайных чисел, вызывающая несоответствие порядка.
Я скомпилировал с g++
версии 5.3.0, в Linux, с флагом -std=c++11
.
Заранее благодарим за помощь.
Ответы
Ответ 1
x << y
является синтаксическим сахаром для вызова функции operator<<(x, y)
.
Вы помните, что стандарт С++ не накладывает ограничений на порядок оценки аргументов вызова функции.
Таким образом, компилятор может испускать код, который сначала вычисляет x или y.
Из стандарта: §5 примечание 2:
Операторы могут быть перегружены, то есть заданы значения при применении к выражениям типа класса (пункт 9) или тип перечисления (7.2). Использование перегруженных операторов преобразуется в вызовы функций, как описано в 13,5. Перегруженные операторы подчиняются правилам синтаксиса, указанным в пункте 5, но требования тип операнда, категория значения, и порядок оценки заменяются правилами вызова функции.
Ответ 2
Это потому, что порядок оценки этой строки
cout << D1(Generator2) << " " << D1(Generator2) << " " << D1(Generator2) << endl ;
не то, что вы думаете.
Вы можете проверить это следующим образом:
int f() {
static int i = 0;
return i++;
}
int main() {
cout << f() << " " << f() << " " << f() << endl ;
return 0;
}
Выход: 2 1 0
Заказ не указан стандартом С++, поэтому порядок может отличаться для других компиляторов, см. ответ Ричарда Ходжеса.
Ответ 3
Небольшое изменение в программе показывает, что происходит:
#include <fstream>
#include <random>
#include <iostream>
using namespace std ;
int main()
{
mt19937_64 Generator(12187) ;
mt19937_64 Generator2(12187) ;
uniform_int_distribution<int> D1(1,100) ;
cout << D1(Generator) << " " ;
cout << D1(Generator) << " " ;
cout << D1(Generator) << endl ;
cout << D1(Generator2) << " " << D1(Generator2) << " " << D1(Generator2) << endl ;
}
Вывод:
4 48 12
12 48 4
Итак, ваши Генераторы производят равные результаты - но порядок аргументов вашей cout-линии вычисляется в другом порядке.
Попробуйте онлайн:
http://ideone.com/rsoqDe
Ответ 4
Эти строки
cout << D1(Generator) << " " ;
cout << D1(Generator) << " "
<< D1(Generator) << endl ;
cout << D1(Generator2) << " "
<< D1(Generator2) << " "
<< D1(Generator2) << endl ;
потому что D1()
возвращает int, для которого ostream::operator<<()
имеет перегрузку, эффективно вызывает (исключая endl
)
cout.operator<<(D1(Generator));
cout.operator<<(D1(Generator))
.operator<<(D1(Generator));
cout.operator<<(D1(Generator2))
.operator<<(D1(Generator2))
.operator<<(D1(Generator2));
Теперь стандарт должен сказать,
& раздел; 5.2.2 [4]
Когда вызывается функция, каждый параметр должен инициализируется соответствующим аргументом.
[Примечание: такие инициализации неопределенно упорядочены относительно друг друга - примечание конца)
Если функция нестатическая функции-члена, этот параметр функции должен быть равен инициализируется указателем на объект вызова
Итак, разделим предыдущее выражение
cout.operator<<(a()) // #1
.operator<<(b()) // #2
.operator<<(c()); // #3
Чтобы проиллюстрировать конструкцию указателя this
, они концептуально эквивалентны (опуская ostream::
для краткости):
operator<<( // #1
&operator<<( // #2
&operator<<( // #3
&cout,
a()
), // end #3
b()
), // end #2
c()
); // end #1
Теперь посмотрим на вызов верхнего уровня. Что мы оцениваем сначала, #2
, или c()
? Поскольку, как подчеркивается в цитате, порядок неопределен, тогда мы не знаем &mdash, и это верно рекурсивно: даже если бы мы оценили #2
, мы все равно столкнулись бы с вопросом, следует ли оценивать его внутренний #3
или b()
.
Так что, надеюсь, объясняет, что происходит здесь более четко.