我有一个未模板化的函子对象,我试图将其存储为另一个对象中的std::function
。这个对象非常重量级,所以它被标记为不可复制,但它确实有一个move构造函数。但是,尝试从临时构造函数构造std::函数或对其赋值会失败。
下面是一个引起错误的最小示例:
// pretend this is a really heavyweight functor that can't be copied.
struct ExampleTest
{
int x;
int operator()(void) const {return x*2;}
ExampleTest( ) :x(0){}
ExampleTest( int a ) :x(a){}
// allow move
ExampleTest( ExampleTest &&other ) :x(other.x) {};
private: // disallow copy, assignment
ExampleTest( const ExampleTest &other );
void operator=( const ExampleTest &other );
};
// this sometimes stores really big functors and other times stores tiny lambdas.
struct ExampleContainer
{
ExampleContainer( int );
std::function<int(void)> funct;
};
/******** ERROR:
Compiler error: 'ExampleTest::ExampleTest' : cannot access private member
declared in class 'ExampleTest'
******************/
ExampleContainer::ExampleContainer( int x )
: funct( ExampleTest( x ) )
{}
/******** ERROR:
Compiler error: 'ExampleTest::ExampleTest' : cannot access private member
declared in class 'ExampleTest'
******************/
int SetExample( ExampleContainer *container )
{
container->funct = ExampleTest();
return container->funct();
}
在一个更简单的结构中,我只是创建了一个局部函数,我也得到了错误:
int ContrivedExample( )
{
// extra parens to sidestep most vexing parse
std::function<int()> zug( (ExampleTest()) );
/*** ERROR: 'ExampleTest::ExampleTest' : cannot access private member
declared in class 'ExampleTest' */
int troz = zug( ) ;
return troz;
}
据我所知,在所有这些情况下,应该将临时ExampleTest作为右值传递给函数构造函数。但是编译器想要复制它们。
给了什么?是否有可能将不可复制(但移动可复制)的函数对象传递给std::function构造函数?有指针之类的变通方法,但我想了解这里发生了什么。
上面的具体错误来自带有CTP c++ 11补丁的Visual Studio 2012。
这个对象非常重量级,所以它被标记为不可复制的,但它确实有一个move构造函数。
如果一个函子是不可复制的,它不满足std::function
使用的必要条件。c++ 11标准第20.8.11.2.1/7段规定:
template<class F> function(F f); template <class F, class A> function(allocator_arg_t, const A& a, F f);
7 要求:
F
为CopyConstructible
。对于参数类型ArgTypes
,f
应为Callable
(20.8.11.2)并返回类型R
。A
的复制构造函数和析构函数不能抛出异常。
std::函数可以从函子对象的右值移动构造。大多数实现都是这样做的。
std::函数的"我的目标必须是可复制构造的"要求是由于它自己的可复制构造的要求。Std::function的类型仅由其目标的签名定义(例如:void(int)),并且Std::function本身由标准定义为可复制构造的。因此,当您复制构造std::函数时,它需要调用目标函数(底层函子)的复制函数。所以它要求目标有一个。它没有其他选择。
由于要求目标是可复制构造的,标准并没有说当从可右值调用对象构造std::函数时,实现应该复制而不是移动。该实现可能只调用可调用对象的move- actor。
带有示例和测试的更详细的附加信息:
例如在gcc(MSVC类似)中,从任何可调用对象中实现std::函数的actor:
template<typename _Res, typename... _ArgTypes>
template<typename _Functor, typename>
function<_Res(_ArgTypes...)>::
function(_Functor __f)
: _Function_base()
{
typedef _Function_handler<_Signature_type, _Functor> _My_handler;
// don't need to care about details below, but when it uses __f, it
// either uses std::move, or passes it by references
if (_My_handler::_M_not_empty_function(__f))
{
_My_handler::_M_init_functor(_M_functor, std::move(__f));
_M_invoker = &_My_handler::_M_invoke;
_M_manager = &_My_handler::_M_manager;
}
}
传递"_Functor __f"参数的值,如果有移动构造函数,则使用其移动构造函数,如果没有移动构造函数,则使用其复制构造函数。如下面的测试程序所示:
int main(){
using namespace std;
struct TFunctor
{
TFunctor() = default;
TFunctor(const TFunctor&) { cout << "cp ctor called" << endl; }
TFunctor(TFunctor&&) { cout << "mv ctor called" << endl; };
void operator()(){}
};
{ //!!!!COPY CTOR of TFunctor is NEVER called in this scope
TFunctor myFunctor;
//TFunctor move ctor called here
function<void()> myStdFuncTemp{ std::move(myFunctor) };
function<void()> myStdFunc{ move(myStdFuncTemp) };
}
{ //TFunctor copy ctor is called twice in this scope
TFunctor myFunctor;
//TFunctor copy ctor called once here
function<void()> myStdFuncTemp{ myFunctor };
//TFunctor copy ctor called once here
function<void()> myStdFunc{ myStdFuncTemp };
}
}
最后,您可以创建一个与std::function几乎完全相同的unstd::function_only_movable,但它删除了自己的复制函数,因此它不需要要求目标可调用对象具有一个复制函数。您还需要只从可调用对象的右值构造它。