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

kar*_*oss 36 c++ random c++11

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

Yuu*_*shi 22

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

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;
};
Run Code Online (Sandbox Code Playgroud)

如果我们斜视所有的_,我们可以看到它只有一个成员参数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!
Run Code Online (Sandbox Code Playgroud)

这只是理论化,但我想它不受限制的原因const是允许实现者在需要时改变它们的实现.此外,它保持一个更统一的界面 - 如果有些operator()是非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";
}
Run Code Online (Sandbox Code Playgroud)

这里,输出格式是mu sigma nextval.

  • @Yuushi:标准拒绝隐藏可变性,因为它旨在保证可以从多个线程安全地调用对象的`const`操作.这就是为什么,例如,C++ 11标准现在禁止COW. (6认同)
  • @ R.MartinhoFernandes对于像`std :: uniform_int_distribution`这样的东西,你*可以让它每次都进行一次新的发布,即使实现也是如此.分布中的图号应该(理论上)不以任何方式修改分布本身.如果我从mu = 0和sigma = 1的正态分布中绘制一个数字,则分布仍然是正态分布,其后mu = 0且sigma = 1. (5认同)
  • 尽管共享相同的名称,但C++中的分布与数学分布的实体不同.这是你必须接受的.事实上,一些C++发行版具有可变状态*并且该状态是可观察的*(观察它可能不容易*因为我们讨论的是随机性和概率):编写假设没有可观察状态的代码分布不均的输出.并且`mutable`绝不应该用于隐藏可观察状态. (3认同)
  • @karlicoss:我不确定整个库中的每个*单一访问,但我知道这是一个设计目标,并且已经对所有STL容器和`basic_string`类强制执行. (2认同)
  • @R.MartinhoFernandes “尽管名称相同,但 C++ 中的分布与数学中的分布并不是同一个实体。这是你必须接受的东西” OP 正在问为什么会这样,所以这并没有真正回答问题(这个答案似乎认为这是一个实现细节,有点弱)。 (2认同)