在 C++03 和 C++14 的代码中进行简单随机改组的最佳做法是什么?

What are best practices for simple random shuffling in code that's both C++03 and C++14?

背景:我正在为一个简单的游戏洗牌向量的元素。应该可以通过传递相同的整数种子来重新玩同一个游戏——反之亦然,不同的种子应该产生不同的游戏。密码安全性(或任何严格性)不是设计目标;代码的简洁性 的设计目标。

C++98/C++03引入std::random_shuffle,这样使用:

int seed = ...;
std::srand(seed);  // caveat, see below

std::vector<int> deck = ...;
std::random_shuffle(deck.begin(), deck.end());

但是,从 C++14 开始,random_shuffle 已被弃用(来源:N3924)。洗牌的 C++14 方式是

int seed = ...;

std::vector<int> deck = ...;
std::shuffle(deck.begin(), deck.end(), std::mt19937(seed));

以下是每种方法的缺点:

我想到的第一个候选人是:

第二个候选人是:

但是有什么好的方法可以解决这个问题吗?我知道这是一个没有人会遇到的问题,除非他们的程序是一个玩具程序;但是一定有 数百个 玩具程序遇到了这个问题!

boost::random::mt19937 作为备用?我为线程执行此操作时无需担心。

首先,开发一个想要的界面。 它应该向用户隐藏任何 platform/compiler 细节,但会为您提供实施所需的所有数据。 编写具有所需用法的单元测试。像这样:

int seed = ...;
std::vector<int> deck = ...;
my_shuffle(deck.begin(), deck.end(), seed);

然后实施

template< typename IteratorType >
void my_shuffle( IteratorType first, IteratorType last, 
                                       int seed = some_default_seed_maybe )
{
     #ifdef MACOS_FOUND
         // Mac solution
     #elif __cplusplus >= 201103L
         // C++11 solution
     #else
         // fallback
}

看起来够干净吗?

同时检查:How to detect reliably Mac OS X, iOS, Linux, Windows in C preprocessor?

即使在 C++11 中,分布在实现中也没有标准化。

编写您自己的洗牌器(对于每个元素,将其与另一个随机元素交换)和随机数 generator/distribution。弱、慢的随机数生成器短而简单。

我会把你的 'random factory' 传递下去,并在线程生成时使用 'fork' 的方法,因为这样做还可以让你在同一执行中执行多个 'runs'。具有显式状态而不是全局状态通常是值得的。但如果是单线程则不需要:只需将随机工厂塞入某个全局状态并屏住你的鼻子。

不存在将 C++03 连接到 C++17 的随机混编,因此请使用简略的手写体。它还确保了在多个平台上的相同行为,这出于多种原因很有用(测试覆盖率(在不同平台上相同),跨平台测试(OS/X 上的错误可以在 Windows 上调试),无数东西的可移植性(保存游戏文件、基于 io 的网络游戏等)。

我可能会想做这样的事情。它通过将其包装在自定义 class 中解决了 "passing down the seed" 问题。为了减少工作量,我私下继承了 std::vector<int> 并实现了 deck.

实际需要的那些功能

私有继承通过确保我不能将 deck* 分配给它的基指针(从而避免非虚拟析构函数问题)来为我提供一些保护。

#if __cplusplus >= 201103L

class deck
: private std::vector<int>
{
    int seed = 0;

public:
    deck(int seed = 0): seed(seed) {}

    // access relevant functions
    using std::vector<int>::size;
    using std::vector<int>::begin;
    using std::vector<int>::end;
    using std::vector<int>::push_back;
    using std::vector<int>::operator=;
    using std::vector<int>::operator[];

    void shuffle()
    {
        std::shuffle(begin(), end(), std::mt19937(seed));
    }

};

#else

class deck
: private std::vector<int>
{
    typedef std::vector<int> vector;
    typedef vector::iterator iterator;

    int seed = 0;

public:
    deck(int seed = 0): seed(seed) {}

    // implement relevant functions
    iterator begin() { return vector::begin(); }
    iterator end() { return vector::end(); }
    void push_back(int i) { vector::push_back(i); }
    int& operator[](vector::size_type i) { return (*this)[i]; }

    void shuffle()
    {
        std::srand(seed);
        std::random_shuffle(begin(), end());
    }

};

#endif

int main()
{
    deck d(5);

    d = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    d.shuffle();

    for(unsigned i = 0; i < d.size(); ++i)
        std::cout << d[i] << '\n';
}