我第一次开始使用C ++ 11的<random>
标题,但仍有一些事情看起来有些神秘。这个问题是关于完成一项非常简单的任务的预期的,惯用的,最佳实践的方法。
目前,在我的代码的一部分中,我有类似的内容:
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};
然后当我想要一个介于0和B之间的整数时,我会调用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);
}
但这涉及每次创建一个新的分发对象,并且我得到的印象不是你应该这样做的。
所以我的问题是,使用<random>
标题提供的抽象来完成这个简单任务的预期最佳实践方法是什么?我问,因为我以后想要做比以后更复杂的事情,我想确保我以正确的方式使用这个系统。
答案 0 :(得分:7)
uniform_int_distribution
构建起来不应该是昂贵的,因此每次使用新限制创建一个应该没问题。但是,有一种方法可以使用具有新限制的同一对象,但这很麻烦。
uniform_int_distribution::operator()
有一个重载,它带有一个uniform_int_distribution::param_type
对象,可以指定要使用的新限制,但param_type
本身是一个不透明的类型,并且没有可移植的方法来构造一个除了从现有uniform_int_distribution
实例中提取它。例如,以下函数可用于构造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();
}
将这些传递给operator()
,生成的结果将在指定的范围内。
因此,如果您真的想重复使用相同的uniform_int_distribution
,请使用上述函数创建并保存param_type
的多个实例,并在调用operator()
时使用这些实例。
上面的答案是不准确的,因为标准确实指定param_type
可以使用与相应分布类型的构造函数使用的相同的分布参数构造。感谢@ T.C. pointing this out。
来自§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)...);
}
可以用作
make_param_type<std::uniform_int_distribution<>>(0, 10)
答案 1 :(得分:7)
回答我自己的问题:通过调整this document中的示例,以下似乎是实现函数返回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);
}
为了使其成为线程安全的,必须避免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;
};
这是用(例如)Random<std::default_random_engine> rnd
实例化的,然后可以用rnd.integer(n)
获得随机整数。从其他分布中采样的方法可以很容易地添加到这个类中。
为了重复我在评论中所说的,重复使用分布对象可能不需要统一采样整数的特定任务,但对于其他分布,我认为这将比每次创建它更有效,因为有一些算法从一些可以通过同时生成多个值来节省CPU周期的分布中进行采样。 (原则上,即使uniform_int_distribution
也可以通过SIMD矢量化来实现这一目标。)如果您不能通过保留分发对象来提高效率,那么很难想象为什么他们会以这种方式设计API
Hooray for C ++及其不必要的复杂性!下午结束了一个下午的工作,完成了一个简单的五分钟任务,但至少我对我现在所做的事情有了更好的了解。
答案 2 :(得分:2)
根据不同参数生成代码的惯用方法是根据需要创建分发对象,Vary range of uniform_int_distribution:
std::random_device rd;
std::default_random_engine eng{rd()};
int n = std::uniform_int_distribution<>{0, A}(eng);
如果您担心未能充分利用分发的内部状态可能会影响性能,您可以使用单个分发并每次传递不同的参数:
std::random_device rd;
std::default_random_engine eng{rd()};
std::uniform_int_distribution<> dist;
int n = dist(eng, decltype(dist)::param_type{0, A});
如果这看起来很复杂,请考虑到大多数情况下,您将根据具有相同参数的相同分布生成随机数(因此分布构造函数采用参数);通过改变参数,您已进入高级领域。