为什么c ++ 11随机分布是可变的?

时间:2013-04-15 05:35:56

标签: c++ random c++11

我认为c ++ 11随机分布(例如uniform_int_distribution)生成的值仅取决于传递给operator()的生成器的状态。但是,由于某种原因,const的签名中没有operator()说明符。这是什么意思,我应该如何将分布作为函数参数传递?我认为我必须将它作为任何非相互参数传递:通过const引用,但现在我不确定。

2 个答案:

答案 0 :(得分:21)

我最初误解了这个问题,然而,现在我明白了,这是一个很好的问题。一些挖掘g ++ <random>实现的来源给出了以下内容(为清楚起见,省略了一些内容):

template<typename _IntType = int>
  class uniform_int_distribution
  {

  struct param_type
  {
    typedef uniform_int_distribution<_IntType> distribution_type;

    explicit
    param_type(_IntType __a = 0,
       _IntType __b = std::numeric_limits<_IntType>::max())
    : _M_a(__a), _M_b(__b)
    {
      _GLIBCXX_DEBUG_ASSERT(_M_a <= _M_b);
    }

     private:
    _IntType _M_a;
    _IntType _M_b;
};

public:
  /**
   * @brief Constructs a uniform distribution object.
   */
  explicit
  uniform_int_distribution(_IntType __a = 0,
           _IntType __b = std::numeric_limits<_IntType>::max())
  : _M_param(__a, __b)
  { }

  explicit
  uniform_int_distribution(const param_type& __p)
  : _M_param(__p)
  { }

  template<typename _UniformRandomNumberGenerator>
result_type
operator()(_UniformRandomNumberGenerator& __urng)
    { return this->operator()(__urng, this->param()); }

  template<typename _UniformRandomNumberGenerator>
result_type
operator()(_UniformRandomNumberGenerator& __urng,
       const param_type& __p);

  param_type _M_param;
};

如果我们斜视所有_,我们可以看到它只有一个成员参数param_type _M_param,它本身只是一个包含2个整数值的嵌套结构 - 实际上是一个范围。 operator()仅在此处声明,未定义。更多挖掘将我们带入了定义。代替在这里发布所有代码,这是相当丑陋(而且相当长),只需说这个函数内没有任何变异。实际上,将const添加到定义和声明中将很乐意编译。

那么问题就变成了,对于其他所有发行版都是如此吗?答案是不。如果我们查看std::normal_distribution的实现,我们会发现:

template<typename _RealType>
template<typename _UniformRandomNumberGenerator>
  typename normal_distribution<_RealType>::result_type
  normal_distribution<_RealType>::
  operator()(_UniformRandomNumberGenerator& __urng,
     const param_type& __param)
  {
result_type __ret;
__detail::_Adaptor<_UniformRandomNumberGenerator, result_type>
  __aurng(__urng);

    //Mutation!
if (_M_saved_available)
  {
    _M_saved_available = false;
    __ret = _M_saved;
  }
    //Mutation!

这只是理论化,但我想它不限于const的原因是允许实施者在需要时改变它们的实现。此外,它保持了更加统一的界面 - 如果某些operator()const而某些{ - 1}}为非const,则一切都变得有点混乱。

然而,为什么他们不是简单地让它们成为const并让实施者利用mutable我不确定。可能,除非这里的某个人参与了标准化工作的这一部分,否则你可能无法得到一个好的答案。

编辑:正如MattieuM指出的那样,mutable和多个线程不能很好地协同工作。

除了一个有趣的外,std::normal_distribution一次生成两个值,缓存一个(因此_M_saved)。它定义的operator<<实际上允许您在下次调用operator()之前看到此值:

#include <random>
#include <iostream>
#include <chrono>

std::default_random_engine eng(std::chrono::system_clock::now().time_since_epoch().count());
std::normal_distribution<> d(0, 1);

int main()
{
   auto k = d(eng);
   std::cout << k << "\n";
   std::cout << d << "\n";
   std::cout << d(eng) << "\n";
}

此处输出格式为mu sigma nextval

答案 1 :(得分:1)

另一个答案是:

这仅是理论上的问题,但我想它之所以不限于const的原因是允许实现者根据需要更改其实现。此外,它保持了更统一的接口-如果某些operator()为const,而某些为非const,则一切都会变得有些混乱。

这在大多数情况下是正确的,但是比通用编程的背景要深得多。 (正如@Calimo所说的,这使const只是为了“以防万一”而被忽略了。)

考虑这一点后,我得出了一个结论,问题在于以下原则是否可以是 const的成员函数实际上取决于{{1 }}。

_UniformRandomNumberGenerator

在(通用)规范的这个级别上还不知道,所以只有到那时“ [规范]才允许实现者对[内部状态]进行突变”,并且为了通用起见。

因此,稳定性的问题是在编译时应该知道template<typename _UniformRandomNumberGenerator> result_type operator()(_UniformRandomNumberGenerator& __urng) 是否能够生成足够的随机性(位)以进行分布以产生样本绘制。 / p>

在当前规范中,可能性被排除在外,但原则上可以通过具有成员函数的两个互斥版本来实现(或指定):

_UniformRandomNumberGenerator

template<typename _URG, typename = std::enable_if<not has_enough_randomness_for<_URG, result_type>::value > > result_type operator()(_UniformRandomNumberGenerator& __urng){..statefull impl..} template<typename _URG, typename = std::enable_if<has_enough_randomness_for<_URG, result_type>::value > > result_type operator()(_UniformRandomNumberGenerator& __urng) const{..stateless impl...} 是一个虚构的布尔元函数,可以判断特定实现是否可以是无状态的。

但是,通常还有另一个障碍,即实现是否是无状态的取决于发行版的运行时参数。 但是由于这是运行时信息,因此不能作为类型系统的一部分传递!

如您所见,这将打开另一罐蠕虫。原则上,has_enough_randomness_for个分布参数可以检测到这一点,但我完全理解委员会在此停止。

如果您需要一个不变的分布(例如,从概念上讲是正确的),则可以通过支付价格轻松实现:

  1. 每次使用前都要复制原始发行版。
  2. 以无状态方式自己实现分发逻辑。

(1)可能效率很低,(2)可能效率低下,极端难以正确实施。

由于一般来说(2)几乎不可能正确,即使正确,它也会有些效率低下,我只想展示如何实现一个有效的无状态分布:

constexpr

template<class Distribution> struct immutable : Distribution{ using Distribution::Distribution; using Distribution::result_type; template<typename _URG> result_type operator()(_URG& __urng) const{ auto dist_copy = static_cast<Distribution>(*this); return dist_copy(__urng); } // template<typename _URG> result_type operator()(_URG& __urng) = delete; }; 替代immutable<D>的方式。 (D的另一个名称可能是immutable<D>。)

例如,我已经用conceptual<D>进行了测试,uniform_real_distribution的替换速度几乎是它的两倍(因为它可以复制/修改/丢弃标称状态),但是正如您指出的那样,可以使用它如果这对您的设计很重要(据我所知),则可以在更“概念”的上下文中进行。

(另一个不相关的小优点是,您可以在线程之间使用共享的不可变分配)


错误的说明性代码如下:

为了说明这样做有多么困难(2),我将对immutable进行天真专业化,该专业化对于某些用途几乎是正确的(或者根据您是谁而非常不正确)问。)

immutable<std::uniform_int_distribution>

此无状态实现非常“有效”,但对于template<class Int> struct immutable<std::uniform_int_distribution<Int>> : std::uniform_int_distribution<Int>{ using std::uniform_int_distribution<Int>::uniform_int_distribution; using std::uniform_int_distribution<Int>::result_type; template<typename _URG> result_type operator()(_URG& __urng) const{ return __urng()%(this->b() - this->a()) + this->a(); // never do this ;) for serious stuff, it is wrong in general for very subtle reasons } // template<typename _URG> result_type operator()(_URG& __urng) = delete; }; a(分布的限制)的任意值,并非100%正确。 如您所见,对于其他发行版(包括连续发行版),此路径非常困难,棘手且容易出错,因此我不建议这样做。


这主要是个人意见:情况是否可以改善?

是的,但是只有一点。

该发行版可能有两个版本的b,一个是无operator()(即const),这是最佳版本(当前版本),另一个是&它不能修改什么状态。但是,尚不清楚它们是否必须在确定性上保持一致(即给出相同的答案)。 (即使复制到后备版本,其结果也不会与完整的可变分布相同。)但是,我认为这不是一条可行的道路(同意其他答案)。您可以使用一个不变的版本,也可以同时使用一个不变的版本。

我认为可以做的是拥有一个可变的版本,但要为r值引用(const)提供特定的重载。 这样,可以使用可变版本的机制,但是可以省略更新(例如,重置)状态的现在“无用”的步骤,因为将不再使用特定实例。这样一来,在某些情况下可以节省一些操作。

这样,上述operator() &&适配器可以用这种方式编写并利用语义:

immutable