在同时为 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
)"设置它并忘记它",而不必定义我自己的类型mt19937
的全局 PRNG。 换句话说:我不想被PRNG细节所困扰,我只想洗牌我的向量!
我有点担心线程安全性(能够同时从不同的线程洗牌两个不同的甲板),但显然不是同时担心线程安全性和"可播种性"。将线程安全视为"可有可无"。
我想到的第一个候选人是:
咬紧牙关,
int seed
一直传递到甲板洗牌代码中(避免全局变量)使用类似
#if __cplusplus >= 20110000
的内容来使用 C++11 之前的random_shuffle
和 C++11 之后的shuffle
要解决 OSX 上
srand
"错误",请使用带有一些复杂函子的三参数版本的random_shuffle
......这听起来很丑
第二个候选者是:
- 螺丝 C++03;只需放弃对任何不提供
std::shuffle
的支持,开箱即用std::mt19937
但是有没有解决这个问题的好方法?我知道这是一个没有人有的问题,除非他们的程序是玩具程序;但是一定有数百个玩具程序遇到了这个问题!
首先,开发一个所需的接口。它应该对用户隐藏任何平台/编译器细节,但为您提供实现所需的所有数据。编写具有所需用法的单元测试。像这样:
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
}
它看起来足够干净吗?
另请检查: 如何在 C 预处理器中可靠地检测 Mac OS X、iOS、Linux、Windows?
即使在 C++11 中,发行版在实现中也没有标准化。
编写自己的洗牌器(对于每个元素,将其与另一个随机序列交换)和随机数生成器/分布。 弱、慢的随机数生成器简短而简单。
我会将您的"随机工厂"向下传递,并且是一种在线程生成时"分叉"的方法,因为这样做还可以让您在同一次执行中执行多次"运行"。 使用显式状态而不是全局状态通常是值得的。 但如果是单线程,则不需要:只需将随机工厂塞入某个全局状态并捏住鼻子即可。
没有随机的洗牌可以桥接C++03到C++17,所以要拥抱一个简短的手写。 它还确保在多个平台上具有相同的行为,这很有用,原因有很多(测试覆盖率(在不同平台上相同),跨平台测试(OS/X上的错误可以在Windows上调试),无数东西的可移植性(保存游戏文件,基于io的网络游戏玩法等))。
boost::random::mt19937
作为后备吗?我为线程执行此操作,几乎没有担心。
我可能会想做这样的事情。它通过将种子包装在自定义类中来解决"传递种子"问题。为了减少工作量,我私下从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';
}
- 为什么随机数生成器不在void函数中随机化数字,而在main函数中随机化
- 在c++中用vector填充一个简单的动态数组
- 为什么 Serial.println(<char[]>);返回随机字符?
- (C++)分析树以计算返回错误值的简单算术表达式
- 字符串-C++后显示的随机字符
- 我的简单if-else语句是如何无法访问的代码
- 使用简单类型列表实现的指数编译时间.为什么
- 循环中的随机函数
- 在c++构造函数中使用随机字符串生成器
- 如何在BST的这个简单递归实现中消除警告
- 一种在C++中读取TXT配置文件的简单方法
- 使用std::mt19937从字符串中返回一个随机单词
- 关于简单C++函数(is_palindrome)的逻辑的问题
- 堆缓冲区溢出随机发生。对于一个简单的代码?(我是C++新手)
- 简单的代码,看似随机的结果——这是由于过时的引用造成的吗
- C++,mpi:创建随机数据的最简单方法
- 在同时为 C++03 和 C++14 的代码中进行简单随机洗牌的最佳实践是什么?
- c++中简单的类型驱动随机模型构造
- 随机运行失败与简单的计数项目
- 简单的随机漫步程序,带有一个奇怪的setw错误