Порядок случайных чисел в C++ с использованием

у меня есть следующий код, который я написал, чтобы проверить часть большей программы:

#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.

заранее спасибо за вашу помощь.

4 ответов


x << y является синтаксическим сахаром для вызова функции operator<<(x, y).

вы помните, что стандарт c++ не ограничивает порядок оценки аргументов вызова функции.

таким образом, компилятор может свободно испускать код, который сначала вычисляет x или y.

из стандарта: §5 примечание 2:

операторы могут быть перегружены, то есть заданы значения при применении к выражениям типа класса (предложение 9) или тип перечисления (7.2). использование перегруженных операторов преобразуется в вызовы функций, как описано в 13.5. Перегруженные операторы подчиняются правилам синтаксиса, указанным в пункте 5, но требованиям тип операнда, категория значения,и порядок оценки заменяются правилами для вызова функции.


это потому, что порядок оценки этой строки

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


порядок не указан стандартом C++, поэтому порядок может отличаться от других компиляторов, см. ответ Ричарда Ходжеса.


небольшое изменение в программе показывает, что происходит:

#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


эти строки

  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()? Поскольку, как подчеркивается в цитате, порядок неопределен, то мы не знаем-и это верно рекурсивно: даже если мы оценили #2, мы все равно столкнулись бы с вопросом, стоит ли оценивать его внутренний #3 или b().

так что, надеюсь, это объясняет, что здесь происходит более четко.