为什么c++11随机分布是可变的
Why are c++11 random distributions mutable?
我认为c++11随机分布(例如uniform_int_distribution
(生成的值仅取决于传递给operator()
的生成器的状态。但是,由于某些原因,在operator()
的签名中没有const
说明符。这意味着什么,我应该如何将分布作为函数参数传递?我认为我必须将它作为任何不可变的参数传递:通过const引用,但现在我不确定。
起初我误解了这个问题,但现在我明白了,这是一个好问题。对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
,而一些是非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
。
另一个答案是:
这一切都只是理论化的,但我想它不局限于const的原因是允许实现者在需要时对其实现进行变异。此外,它保持了一个更统一的接口——如果一些运算符((是常量,而一些是非常量,那么这一切都会变得有点混乱。
这在很大程度上是正确的,但它甚至比泛型编程中的更深入。(正如@Calimo所说,这留下了省略const
只是"以防万一"的想法(。
经过思考,我得出的结论是,以下成员函数原则上是否可以const
的问题实际上取决于_UniformRandomNumberGenerator
的实际类型。
template<typename _UniformRandomNumberGenerator>
result_type
operator()(_UniformRandomNumberGenerator& __urng)
在这个级别的(通用(规范中,这是不知道的,所以只有在那时"[规范]允许实现者改变[内部状态]",它这样做是为了通用性。
因此,constness的问题是在编译时应该知道_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(它可能有点低效,并且非常难以正确实现。
由于(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;
};
以CCD_ 28替代CCD_ 29的方式。(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;
};
这种无状态实现非常"高效",但对于a
和b
的任意值(分布的限制(来说并不是100%正确。正如你可能看到的,对于其他发行版(包括连续发行版(,这条路径非常困难、棘手且容易出错,所以我不推荐它
这主要是个人观点:情况能得到改善吗
是的,但只是轻微的。
分发版可以有两个版本的operator()
,一个是无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);
}
};
- MSVC是否支持C++11样式的属性而不是__declspec
- 创建LinkedList退出,返回代码为-11(SIGSEGV)
- 我可以将一个用clang c++11编译的对象与另一个用c++17编译的对象链接起来吗
- 继承:构造函数,初始化C++11中基类的类C数组成员
- 如何将模板转换为C++11之前的模板
- c++11评估顺序(未定义的行为)
- C++中的VLA,扩展名为std=C++11
- 代码在我的计算机上运行良好,但是在将其提交给coursera时遇到未知的信号11问题
- "类模板示例<int>;"语句对 C++11 是什么意思?
- this_thread::sleep_for和计时时钟之间的关系是否由C++11标准指定
- 多态性与C++11分布
- C++11 与不同类型的随机分布共享相同的函数
- 对 C++11 随机分布的界面设计感到困惑
- C++11 随机数分布在各个平台上并不一致——有什么替代方案
- 使用一个随机引擎在c++11中实现多个分布
- C++11交叉编译器/标准库随机分布再现性
- C++11 类的分布如何<random>转换底层生成器?
- 随机数分布c++11
- 一个C++11随机分布是由什么组成的
- 为什么c++11随机分布是可变的