我刚刚开始<random>第一次使用C++ 11的标题,但仍有一些东西看起来有点神秘.这个问题是关于完成一项非常简单的任务的预期的,惯用的,最佳实践方法.
目前,在我的代码的一部分我有这样的事情:
std::default_random_engine eng {std::random_device{}()};
std::uniform_int_distribution<> random_up_to_A {0, A};
std::uniform_int_distribution<> random_up_to_B {0, B};
std::uniform_int_distribution<> random_up_to_some_other_constant {0, some_other_constant};
Run Code Online (Sandbox Code Playgroud)
然后当我想要一个0和BI之间的整数调用random_up_to_B(eng).
由于这开始看起来有点傻,我想实现一个函数rnd,rnd(n, eng)返回0到n之间的随机整数.
像下面这样的东西应该工作
template <class URNG>
int rnd(int n, URNG &eng) {
std::uniform_int_distribution<> dist {0, n};
return dist(eng);
}
Run Code Online (Sandbox Code Playgroud)
但这涉及每次创建一个新的分发对象,我得到的印象不是你应该这样做的方式.
所以我的问题是,使用<random>标题提供的抽象来完成这个简单任务的预期最佳实践方法是什么?我问,因为我一定要做比以后更复杂的事情,我想确保我以正确的方式使用这个系统.
uniform_int_distribution构造起来不应该昂贵,所以每次创建一个新的限制都应该没问题.但是,有一种方法可以使用具有新限制的相同对象,但这很麻烦.
uniform_int_distribution::operator()有一个重载,它接受一个uniform_int_distribution::param_type可以指定要使用的新限制的对象,但param_type它本身是一个不透明的类型,并且除了从现有uniform_int_distribution实例中提取它之外,没有可移植的方法来构造它.例如,以下函数可用于构造a uniform_int_distribution::param_type.
std::uniform_int_distribution<>::param_type
make_param_type(int min, int max)
{
return std::uniform_int_distribution<>(min, max).param();
}
Run Code Online (Sandbox Code Playgroud)
将这些传递给operator(),生成的结果将在指定的范围内.
因此,如果您真的想重用它uniform_int_distribution,请创建并保存param_type使用上述函数的多个实例,并在调用时使用它们operator().
上面的答案是不准确的,因为标准确实指定param_type可以使用与相应分布类型的构造函数使用的相同的分布参数构造.感谢@TC 指出这一点.
来自§26.5.1.6/ 9 [rand.req.dist]
对于
D获取与分布参数相对应的参数的每个构造函数,P应具有相同的构造函数,这些构造函数具有相同的要求并且在数量,类型和默认值方面具有相同的参数....
所以我们不需要不必要地构造分布对象来提取param_type.相反,该make_param_type功能可以修改为
template <typename Distribution, typename... Args>
typename Distribution::param_type make_param_type(Args&&... args)
{
return typename Distribution::param_type(std::forward<Args>(args)...);
}
Run Code Online (Sandbox Code Playgroud)
可以用作
make_param_type<std::uniform_int_distribution<>>(0, 10)
Run Code Online (Sandbox Code Playgroud)
回答我自己的问题:通过调整本文档中的示例,以下似乎是实现函数返回0到n-1之间的随机整数的正确方法:
template<class URNG>
int rnd(int n, URNG &engine) {
using dist_t = std::uniform_int_distribution<>;
using param_t = dist_t::param_type;
static dist_t dist;
param_t params{0,n-1};
return dist(engine, params);
}
Run Code Online (Sandbox Code Playgroud)
为了使其成为线程安全的,必须避免static声明.一种可能性就是沿着这些线创建一个便利类,这就是我在我自己的代码中使用的:
template<class URNG>
class Random {
public:
Random(): engine(std::random_device{}()) {}
Random(typename std::result_of<URNG()>::type seed): engine(seed) {}
int integer(int n) {
std::uniform_int_distribution<>::param_type params {0, n-1};
return int_dist(engine, params);
}
private:
URNG engine;
std::uniform_int_distribution<> int_dist;
};
Run Code Online (Sandbox Code Playgroud)
这用(例如)实例化,Random<std::default_random_engine> rnd然后可以获得随机整数rnd.integer(n).从其他分布中采样的方法可以很容易地添加到这个类中.
为了重复我在评论中所说的,重复使用分布对象可能不需要统一采样整数的特定任务,但对于其他分布,我认为这将比每次创建它更有效,因为有一些算法可以从中采样一些可以通过同时生成多个值来节省CPU周期的发行版.(原则上甚至uniform_int_distribution可以通过SIMD矢量化来实现这一点.)如果你不能通过保留分发对象来提高效率,那么很难想象为什么他们会以这种方式设计API.
Hooray for C++及其不必要的复杂性!这结束了下午的工作完成了一个简单的五分钟任务,但至少我对我现在正在做的事情有了更好的了解.
| 归档时间: |
|
| 查看次数: |
914 次 |
| 最近记录: |