可以将std::function从右值引用移动构造到临时函数对象吗?



我有一个未模板化的函子对象,我试图将其存储为另一个对象中的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 要求: FCopyConstructible 。对于参数类型ArgTypes, f应为Callable (20.8.11.2)并返回类型RA的复制构造函数和析构函数不能抛出异常。

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,但它删除了自己的复制函数,因此它不需要要求目标可调用对象具有一个复制函数。您还需要只从可调用对象的右值构造它。

最新更新