使用c ++ 11的<random>标头,获取0到n之间的整数的正确方法是什么?

Nat*_*iel 15 c++ random c++11

我刚刚开始<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>标题提供的抽象来完成这个简单任务的预期最佳实践方法是什么?我问,因为我一定要做比以后更复杂的事情,我想确保我以正确的方式使用这个系统.

Pra*_*ian 7

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)

现场演示


Nat*_*iel 7

回答我自己的问题:通过调整本文档中的示例,以下似乎是实现函数返回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++及其不必要的复杂性!这结束了下午的工作完成了一个简单的五分钟任务,但至少我对我现在正在做的事情有了更好的了解.

  • 我也考虑发布这个,但决定反对它因为它不便携.没有要求`param_type`具有可访问的2参数构造函数,尽管你已经完成了对gcc,clang和VS2013的工作. (2认同)
  • @Praetorian实际上有一个要求.N3797§26.5.1.6/ p9:"对于D参数的每个构造函数,该参数对应于分布的参数,P应具有相应的构造函数,这些构造函数具有相同的要求,并且在数量,类型和默认值方面采用相同的参数. ,对于返回与分布参数对应的值的D的每个成员函数,P应具有相同的成员函数,其具有相同的名称,类型和语义. (2认同)