可以将std :: function从rvalue引用移动构造到临时的functor对象吗?

时间:2013-05-19 20:24:40

标签: c++ c++11 std rvalue-reference

我有一个未模仿的仿函数对象,我试图将其作为std::function存储在另一个对象中。这个对象非常重量级,因此它被标记为不可复制,但它确实有一个移动构造函数。但是,尝试从临时构造函数构造std :: function或分配它失败。

以下是引发错误的最小示例。

// 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应该作为rvalue传递给函数构造函数。然而编译器想要复制它们。

是什么给出的?是否可以将不可复制(但可移动复制)的仿函数对象传递给std :: function构造函数?有指针等解决方法,但我想了解这里发生了什么。

上面的具体错误来自Visual Studio 2012和CTP C ++ 11补丁。 GCC 4.8和Clang 3也会出现故障,并带有错误消息。

2 个答案:

答案 0 :(得分:18)

  

这个对象非常重量级,因此它被标记为不可复制,但它确实有一个移动构造函数。

如果仿函数是不可复制的,则它不符合与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 。参数类型f Callable应为ArgTypes(20.8.11.2)   并返回类型RA的拷贝构造函数和析构函数不会抛出异常。

答案 1 :(得分:3)

std :: function可以从仿函数对象的rvalue构造。大多数实现都是这样做的。

std :: function的“我的目标必须是可复制构造的”要求是由于它自己的要求是可复制构造的。 std :: function的类型仅由其目标的签名(例如:void(int))定义,而std :: function本身由标准定义为可复制构造。所以当你复制构造一个std :: function时,它需要调用它的目标(底层函子)的copy-ctor。所以它需要它的目标。它没有其他选择。

要求目标是可复制构造的,当从rvalue可调用对象构造std :: function时,标准并没有说实现应该复制而不是移动。实现可能只会调用可调用对象的move-ctor。

有关示例和测试的更详细的附加信息:

例如,在任何可调用对象的std :: function的ctor的gcc(MSVC类似)实现中:

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 };
    }
}

传递“_Functor __f”参数的值将使用其移动构造函数(如果有),如果它没有移动ctor,它将使用其复制构造函数。如下面的测试程序可以证明:

{{1}}

最后,您可以创建一个unstd :: function_only_movable,它几乎与std :: function完全相同但删除了自己的copy ctor,因此它不需要要求目标可调用对象拥有一个副本ctor。您还需要仅从可调用对象的右值构造它。