37

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

4

2 回答 2

23

起初我误解了这个问题,但是,现在我明白了,这是一个很好的问题。对 for 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它本身只是一个嵌套结构,包含两个整数值 - 实际上是一个范围。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,有些不是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.

于 2013-04-15T06:40:27.107 回答
1

另一个答案说:

这一切都只是理论上的,但我想它不限于 const 的原因是允许实现者在需要时改变他们的实现。此外,它保持了一个更统一的接口——如果一些 operator() 是 const 而一些是非常量,这一切都会变得有点混乱。

这大部分是正确的,但它甚至比泛型编程的上下文更深。const(正如@Calimo 所说,这留下了“以防万一”省略的想法)。

考虑到这一点,我得出的结论是,问题转化为以下成员函数是否可以在原则 const上真正取决于_UniformRandomNumberGenerator.

template<typename _UniformRandomNumberGenerator>
result_type
operator()(_UniformRandomNumberGenerator& __urng)

在(通用)规范的这个级别上,这是不知道的,所以只有这样“[规范]允许实现者改变[内部状态]”并且为了通用性而这样做。

因此,常量的问题在于,在编译时应该知道是否 _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可以判断特定实现是否可以是无状态的。

但是,还有另一个障碍,一般来说,实现是否无状态取决于分布的运行时参数。但由于这是运行时信息,所以它不能作为类型系统的一部分传递!

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

如果您需要一个不可变的分布(例如“概念上”正确),您可以通过付出代价轻松实现:

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

(1) 效率可能非常低; (2) 正确实施可能效率低下且极其棘手。

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

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并且immutable替换速度几乎是两倍(因为它复制/修改/丢弃了名义状态),但是正如您指出的那样,如果这对您很重要,它可以在更“概念”的上下文中使用设计(我能理解)。

(还有另一个不相关的小优势,即您可以跨线程使用共享的不可变分布)


不正确但说明性的代码如下:

为了说明做 (2) 有多么困难,我将做一个幼稚的专业化,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;
};

这种无状态实现非常“高效”,但对于aand的任意值b(分布的限制)并非 100% 正确。正如您可能看到的,对于其他发行版(包括连续发行版),这条路径非常困难、棘手且容易出错,因此我不推荐它。


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

是的,但只是轻微的。

分布可能有两个版本operator(),一个是 no- const(即&),它是最优的(当前一个),一个是const它可以不修改状态的版本。然而,尚不清楚它们是否必须具有确定性一致(即给出相同的答案)。(即使回退到副本也不会给出与完整的可变分布相同的结果。)。但是,我认为这不是一条可行的道路(同意其他答案);您要么使用不可变版本,要么使用不可变版本,但不能同时使用两者。

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

这样,immutable上面描述的适配器就可以用这种方式编写并利用语义:

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 std::move(dist_copy)(__urng);
// or return (Distribution(*this))(__urng);
   }
};
于 2020-02-26T11:06:19.453 回答