返回绑定的本地函数对象会导致未定义的行为吗?
Does returning a bound local function object cause undefined behaviour?
下面我举个例子。该程序编译并运行良好,但我想知道根据 C++11 标准,这是否是理论上未定义的行为;我可以 return 绑定(临时)局部函数对象的结果吗?
示例代码(经过简短编辑):
#include <iostream>
#include <functional>
struct MyFunctionObject
{
inline void operator() ( const std::string& name )
{ std::cout<< "Hello " << name << "!" << std::endl; }
};
std::function< void () > my_greeter( const std::string& name )
{
MyFunctionObject fun;
return std::bind( fun, name );
}
int main()
{
auto g = my_greeter("sheljohn");
g();
}
编辑 原始示例:
#include <random>
#include <functional>
#include <algorithm>
#include <utility>
#include <iostream>
/******************** ********** ********************/
/******************** ********** ********************/
namespace foo {
/**
* Type of the generator returned by the method 'bind' below.
*/
template <class _dist>
using generator_type = std::function< typename _dist::result_type () >;
// ------------------------------------------------------------------------
/**
* Wrapper for C++11 random library.
*/
template <
class _engine = std::mt19937_64,
class _device = std::random_device
>
struct random
{
typedef _engine engine_type;
typedef _device device_type;
typedef random<_engine,_device> self;
template <class _dist>
static inline generator_type<_dist>
bind( _dist& distribution )
{ return std::bind( distribution, self::engine ); }
template <class _dist>
static inline generator_type<_dist>
bind( _dist&& distribution )
{ return std::bind( distribution, self::engine ); }
static engine_type engine;
static device_type device;
};
// Initialize static engine
template <class _engine,class _device>
_device random<_engine,_device>::device;
template <class _engine,class _device>
_engine random<_engine,_device>::engine = _engine( device() );
// ------------------------------------------------------------------------
/**
* Generic binder to standard random distributions.
*
* SO QUESTION: does this cause undefined behaviour?
*/
template <class _dist, class... Args>
constexpr generator_type<_dist> random_generator( Args&&... args )
{ return random<>::bind( _dist( std::forward<Args>(args)... ) ); }
}; // namespace: foo
/******************** ********** ********************/
/******************** ********** ********************/
int main()
{
auto ngen = foo::random_generator< std::normal_distribution<double> >( 0.0, 1.0 );
for(unsigned i=0; i<10; ++i)
std::cout<< ngen() << " " << std::endl;
}
你没有UB,但你真的是运气多于理智。
这里没有未定义的行为,只是因为这里:
return std::bind( distribution, self::engine );
你按值传递 'distribution',这意味着传递的是它的一个副本(尽管 distribution 是一个可变引用)。由于不能在指定位置为同一类型的值和引用重载函数,因此您想要这样做的函数中必须存在各种技巧 - 就是这样:bind() 需要 std::reference_wrapper - 包装类型以传递引用。所以如果你这样做,你肯定会有未定义的行为:
return std::bind( ref(distribution), self::engine );
此处可供您使用。在这种情况下,您将传递一个 真实引用 来绑定,在这种情况下,它是对传递给包含它的函数外部的局部变量的引用。
该标准实际上在 §26.5 中有关于使用随机数生成器调用 bind()
的特定位:
[ Note: These entities are specified in such a way as to permit the binding of any uniform random number generator object e as the argument to any random number distribution object d, thus producing a zero-argument function object such as given by
bind(d,e). —end note ]
所以你所做的是明确允许的。
同样来自 [func.bind.bind]
,描述指出当您调用 bind(F&& f, BoundArgs&&... bound_args)
时(强调我的):
FD
is the type decay_t<F>
,
fd
is an lvalue of type FD
constructed from std::forward<F>(f)
Ti
is the ith type in the template parameter pack BoundArgs
TiD
is the type decay_t<Ti>
ti
is the ith type in the function parameter pack bound_args
tid
is an lvalue of type TiD
constructed from std::forward<Ti>(ti)
- ..
Returns: A forwarding call wrapper g
... The effect of g(,u1, u2, ..., uM)
shall be INVOKE(fd, std::forward<V1>(v1), std::forward<V2>(V2), ... std::forward<VN>(vN), result_of_t<FD cv & (V1, V2, ..., VN)>)
这个部分让我有点困惑,但基本上你传入的仿函数和参数被转发(copied/move)到本地版本,fd
和 tid...
。 bind
不会保留对函子的引用(FD
是 不是 引用类型!),所有参数也是如此( BoundArgs
的 TiD
类型也是 而非 引用类型)。因此,在您的新示例中,即使 fun
和 name
引用的字符串都超出了范围, bind()
结果仍然是一个有效的可调用对象。
这里没有UB
下面我举个例子。该程序编译并运行良好,但我想知道根据 C++11 标准,这是否是理论上未定义的行为;我可以 return 绑定(临时)局部函数对象的结果吗?
示例代码(经过简短编辑):
#include <iostream>
#include <functional>
struct MyFunctionObject
{
inline void operator() ( const std::string& name )
{ std::cout<< "Hello " << name << "!" << std::endl; }
};
std::function< void () > my_greeter( const std::string& name )
{
MyFunctionObject fun;
return std::bind( fun, name );
}
int main()
{
auto g = my_greeter("sheljohn");
g();
}
编辑 原始示例:
#include <random>
#include <functional>
#include <algorithm>
#include <utility>
#include <iostream>
/******************** ********** ********************/
/******************** ********** ********************/
namespace foo {
/**
* Type of the generator returned by the method 'bind' below.
*/
template <class _dist>
using generator_type = std::function< typename _dist::result_type () >;
// ------------------------------------------------------------------------
/**
* Wrapper for C++11 random library.
*/
template <
class _engine = std::mt19937_64,
class _device = std::random_device
>
struct random
{
typedef _engine engine_type;
typedef _device device_type;
typedef random<_engine,_device> self;
template <class _dist>
static inline generator_type<_dist>
bind( _dist& distribution )
{ return std::bind( distribution, self::engine ); }
template <class _dist>
static inline generator_type<_dist>
bind( _dist&& distribution )
{ return std::bind( distribution, self::engine ); }
static engine_type engine;
static device_type device;
};
// Initialize static engine
template <class _engine,class _device>
_device random<_engine,_device>::device;
template <class _engine,class _device>
_engine random<_engine,_device>::engine = _engine( device() );
// ------------------------------------------------------------------------
/**
* Generic binder to standard random distributions.
*
* SO QUESTION: does this cause undefined behaviour?
*/
template <class _dist, class... Args>
constexpr generator_type<_dist> random_generator( Args&&... args )
{ return random<>::bind( _dist( std::forward<Args>(args)... ) ); }
}; // namespace: foo
/******************** ********** ********************/
/******************** ********** ********************/
int main()
{
auto ngen = foo::random_generator< std::normal_distribution<double> >( 0.0, 1.0 );
for(unsigned i=0; i<10; ++i)
std::cout<< ngen() << " " << std::endl;
}
你没有UB,但你真的是运气多于理智。
这里没有未定义的行为,只是因为这里:
return std::bind( distribution, self::engine );
你按值传递 'distribution',这意味着传递的是它的一个副本(尽管 distribution 是一个可变引用)。由于不能在指定位置为同一类型的值和引用重载函数,因此您想要这样做的函数中必须存在各种技巧 - 就是这样:bind() 需要 std::reference_wrapper - 包装类型以传递引用。所以如果你这样做,你肯定会有未定义的行为:
return std::bind( ref(distribution), self::engine );
此处可供您使用。在这种情况下,您将传递一个 真实引用 来绑定,在这种情况下,它是对传递给包含它的函数外部的局部变量的引用。
该标准实际上在 §26.5 中有关于使用随机数生成器调用 bind()
的特定位:
[ Note: These entities are specified in such a way as to permit the binding of any uniform random number generator object e as the argument to any random number distribution object d, thus producing a zero-argument function object such as given by bind(d,e). —end note ]
所以你所做的是明确允许的。
同样来自 [func.bind.bind]
,描述指出当您调用 bind(F&& f, BoundArgs&&... bound_args)
时(强调我的):
FD
is the typedecay_t<F>
,fd
is an lvalue of typeFD
constructed fromstd::forward<F>(f)
Ti
is the ith type in the template parameter packBoundArgs
TiD
is the typedecay_t<Ti>
ti
is the ith type in the function parameter packbound_args
tid
is an lvalue of typeTiD
constructed fromstd::forward<Ti>(ti)
- ..
Returns: A forwarding call wrapper
g
... The effect ofg(,u1, u2, ..., uM)
shall beINVOKE(fd, std::forward<V1>(v1), std::forward<V2>(V2), ... std::forward<VN>(vN), result_of_t<FD cv & (V1, V2, ..., VN)>)
这个部分让我有点困惑,但基本上你传入的仿函数和参数被转发(copied/move)到本地版本,fd
和 tid...
。 bind
不会保留对函子的引用(FD
是 不是 引用类型!),所有参数也是如此( BoundArgs
的 TiD
类型也是 而非 引用类型)。因此,在您的新示例中,即使 fun
和 name
引用的字符串都超出了范围, bind()
结果仍然是一个有效的可调用对象。
这里没有UB