2

In the program, often in different classes are generated random numbers. So I want to create a class that returns a single instance of the generator std::mt19937. I also take into account that some compilers do not work with std::random_device (To do this, check the value of entropy). I have created a class singleton.

#include <iostream>
#include <random>
#include <chrono>

class RandomGenerator
{
public:
    static RandomGenerator& Instance() {
        static RandomGenerator s;
        return s;
    }
    std::mt19937 get();

private:
    RandomGenerator();
    ~RandomGenerator() {}

    RandomGenerator(RandomGenerator const&) = delete;
    RandomGenerator& operator= (RandomGenerator const&) = delete;

    std::mt19937 mt;
};

RandomGenerator::RandomGenerator() {
    std::random_device rd;

    if (rd.entropy() != 0) {
        mt.seed(rd());
    }
    else {
        auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
        mt.seed(seed);
    }
}

std::mt19937 RandomGenerator::get() {
    return mt;
}

int main() {

    std::mt19937 &mt = RandomGenerator::Instance().get();
    std::uniform_real_distribution<double> dist(0.0, 1.0);
    for (std::size_t i = 0; i < 5; i++)
        std::cout << dist(mt) << "\n";

    std::cout << "\n";

    std::mt19937 &mt2 = RandomGenerator::Instance().get();
    std::uniform_real_distribution<double> dist2(0.0, 1.0);
    for (std::size_t i = 0; i < 5; i++)
        std::cout << dist2(mt2) << "\n";

    return 0;
}

But when I get out of class generator std::mt19937, random numbers begin to repeat. How to avoid it?

0.389459
0.68052
0.508421
0.0758856
0.0137491

0.389459
0.68052
0.508421
0.0758856
0.0137491

P.S. Is there a better way to initialize the generator than time?

Solution

Tested this under the following compilers: Visual Studio, MinGW, DevC++.

#include <iostream>
#include <random>
#include <chrono>

class RandomGenerator
{
public:
    static RandomGenerator& Instance() {
        static RandomGenerator s;
        return s;
    }
    std::mt19937 & get();

private:
    RandomGenerator();
    ~RandomGenerator() {}

    RandomGenerator(RandomGenerator const&) = delete;
    RandomGenerator& operator= (RandomGenerator const&) = delete;

    std::mt19937 mt;
};

RandomGenerator::RandomGenerator() {
    std::random_device rd;

    if (rd.entropy() != 0) {
        mt.seed(rd());
    }
    else {
        auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
        mt.seed(seed);
    }
}

std::mt19937 & RandomGenerator::get() {
    return mt;
}

int main() {

    std::mt19937 &mt = RandomGenerator::Instance().get();
    std::uniform_real_distribution<double> dist(0.0, 1.0);
    for (std::size_t i = 0; i < 5; i++)
        std::cout << dist(mt) << "\n";

    std::cout << "\n";

    std::mt19937 &mt2 = RandomGenerator::Instance().get();
    std::uniform_real_distribution<double> dist2(0.0, 1.0);
    for (std::size_t i = 0; i < 5; i++)
        std::cout << dist2(mt2) << "\n";

    return 0;
}
Harrix
  • 547
  • 3
  • 8
  • 17
  • 1
    Youre returning it by value, which means any internal state modification you make by generating random numbers is lost, thus repeating the patter every time you call get() again. – Borgleader May 10 '17 at 18:53
  • 2
    `std::mt19937 get();` returns by value. This code wouldn't compile with a standards compliant compiler: `std::mt19937 &mt = RandomGenerator::Instance().get();`. – juanchopanza May 10 '17 at 18:53
  • 1
    [Generating random numbers](http://dilbert.com/strip/2001-10-25) – Wade Tyler May 10 '17 at 18:54
  • This will not compile. `&mt = RandomGenerator::Instance().get();` – gsamaras May 10 '17 at 18:55
  • 1
    @gsamaras That statement is non-standard, but it compiles in Visual Studio as it's part of the language extensions it likes to enable by default. – François Andrieux May 10 '17 at 18:57
  • @gsamaras Why this will not compile? Visual Studio compiler and MinGW compiler quietly compiles. – Harrix May 10 '17 at 19:02
  • Because `error: non-const lvalue reference to type 'mersenne_twister_engine<...>' cannot bind to a temporary of type 'mersenne_twister_engine<...>'`. That's what you get when working with non-standard compilers :/ – gsamaras May 10 '17 at 19:09
  • @gsamaras What compiler are you using? On DevС++ also compiled. – Harrix May 10 '17 at 19:18
  • @Harrix it works on Visual Studio as an extension. In order to catch subtle non-standard behaviours like this, I know that Visual Studio has a flag for it (might only be on 2017, but it's probably on 2015 too). I don't remember the flag, however. – Justin May 10 '17 at 19:22
  • @Justin What kind of code are you talking about? Code with the correct method you checked out the visual Studio 2015 compiler mingw (QT version 5.8) and devc++. – Harrix May 10 '17 at 19:29
  • @Harrix g++. DevC++ is more ancient than Greece! – gsamaras May 10 '17 at 19:36
  • 1
    @gsamaras True. In gcc bad code do not compile. The corrected version is compiled. – Harrix May 11 '17 at 04:20
  • Found the compiler flag I was talking about; it's in VS 2017: [`/permissive-`](https://blogs.msdn.microsoft.com/vcblog/2016/11/16/permissive-switch/) – Justin May 11 '17 at 06:10

1 Answers1

5

std::mt19937 get(); returns a copy. Every time you call get() you make a copy of the initial state of the engine. mt19937 is a pseudo-random engine, each state produces a predetermined sequence. If the state of two instances are identical, they will produce the same sequence. Make the function return a reference so that the state of the singleton instance is updated with each new number generated.

std::mt19937 & RandomGenerator::get() {
    return mt;
}
François Andrieux
  • 28,148
  • 6
  • 56
  • 87