19

我有一个未模板化的仿函数对象,我试图将其存储为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 应该作为右值传递给函数构造函数。然而编译器想要复制它们。

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

上面的具体错误来自带有 CTP C++11 补丁的 Visual Studio 2012。GCC 4.8 和 Clang 3 也失败了,并带有自己的错误消息。

4

2 回答 2

19

这个对象真的很重量级,所以它被标记为不可复制,但它确实有一个移动构造函数。

如果函子是不可复制的,则它不满足与 . 一起使用的必要要求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。 对于参数类型和返回类型f应为Callable(20.8.11.2) 。的复制构造函数和析构函数不得抛出异常。ArgTypesRA

于 2013-05-19T20:34:00.913 回答
4

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

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

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


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

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

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”的参数值传递将使用它的移动构造函数,如果它有一个,如果它没有移动ctor,它将使用它的复制构造函数。正如下面的测试程序可以演示的那样:

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

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

于 2017-06-08T17:48:47.333 回答