在 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));
以下是每种方法的缺点:
srand
/random_shuffle
方法在 C++14 中已弃用,因此我们不应该使用它。
在某些实现中,random_shuffle
似乎没有从 srand
获取种子,即,使用不同的值播种不会产生不同的输出! (Linux 上的 libstdc++ 没有这个问题,但是 OSX 10.9.5 上的 Xcode 有。)
shuffle
/mt19937
方式不是C++03的一部分,所以我们不能使用它。
shuffle
/mt19937
方式似乎要求我们将种子一直传递到套牌洗牌代码中。对于我的应用程序,我更愿意通过 srand
等隐藏全局变量的机制来 "set it and forget it",而不必定义我自己的 mt19937
类型的全局 PRNG。换句话说:我不想被 PRNG 细节所困扰,我只想洗牌我的向量!
我 am 有点担心线程安全(能够同时从不同线程洗牌两副不同的牌组),但显然不是线程安全和"seedableness"同时。考虑线程安全 a "nice-to-have".
我想到的第一个候选人是:
硬着头皮将 int seed
一直传递到洗牌代码中(避免全局变量)
使用类似 #if __cplusplus >= 20110000
的东西来使用 random_shuffle
pre-C++11 和 shuffle
post-C++11
要解决 OSX 上的 srand
"bug",请使用带有一些复杂仿函数的 random_shuffle
的三参数版本...这听起来很难看
第二个候选人是:
- 拧紧C++03;只是放弃对任何不提供开箱即用
std::shuffle
和 std::mt19937
的实现的支持
但是有什么好的方法可以解决这个问题吗?我知道这是一个没有人会遇到的问题,除非他们的程序是一个玩具程序;但是一定有 数百个 玩具程序遇到了这个问题!
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';
}
背景:我正在为一个简单的游戏洗牌向量的元素。应该可以通过传递相同的整数种子来重新玩同一个游戏——反之亦然,不同的种子应该产生不同的游戏。密码安全性(或任何严格性)不是设计目标;代码的简洁性 是 的设计目标。
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));
以下是每种方法的缺点:
srand
/random_shuffle
方法在 C++14 中已弃用,因此我们不应该使用它。在某些实现中,
random_shuffle
似乎没有从srand
获取种子,即,使用不同的值播种不会产生不同的输出! (Linux 上的 libstdc++ 没有这个问题,但是 OSX 10.9.5 上的 Xcode 有。)shuffle
/mt19937
方式不是C++03的一部分,所以我们不能使用它。shuffle
/mt19937
方式似乎要求我们将种子一直传递到套牌洗牌代码中。对于我的应用程序,我更愿意通过srand
等隐藏全局变量的机制来 "set it and forget it",而不必定义我自己的mt19937
类型的全局 PRNG。换句话说:我不想被 PRNG 细节所困扰,我只想洗牌我的向量!我 am 有点担心线程安全(能够同时从不同线程洗牌两副不同的牌组),但显然不是线程安全和"seedableness"同时。考虑线程安全 a "nice-to-have".
我想到的第一个候选人是:
硬着头皮将
int seed
一直传递到洗牌代码中(避免全局变量)使用类似
#if __cplusplus >= 20110000
的东西来使用random_shuffle
pre-C++11 和shuffle
post-C++11要解决 OSX 上的
srand
"bug",请使用带有一些复杂仿函数的random_shuffle
的三参数版本...这听起来很难看
第二个候选人是:
- 拧紧C++03;只是放弃对任何不提供开箱即用
std::shuffle
和std::mt19937
的实现的支持
但是有什么好的方法可以解决这个问题吗?我知道这是一个没有人会遇到的问题,除非他们的程序是一个玩具程序;但是一定有 数百个 玩具程序遇到了这个问题!
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';
}