【问题标题】:Binding functions with unique_ptr arguments to std::function<void()>将具有 unique_ptr 参数的函数绑定到 std::function<void()>
【发布时间】:2013-12-14 15:07:46
【问题描述】:

我正在尝试使以下代码工作:

#include <cstdio>
#include <functional>
#include <string>
#include <memory>

using namespace std;

class Foo {
public:
    Foo(): m_str("foo") { }

    void f1(string s1, string s2, unique_ptr<Foo> p)
        {
            printf("1: %s %s %s\n", s1.c_str(), s2.c_str(), p->str());
        }

    void f2(string s1, string s2, Foo* p)
        {
            printf("2: %s %s %s\n", s1.c_str(), s2.c_str(), p->str());
        }

    const char* str() const { return m_str.c_str(); }

private:
    string m_str;
};

int main()
{
    string arg1 = "arg1";
    string arg2 = "arg2";
    Foo s;
    unique_ptr<Foo> ptr(new Foo);


    //function<void()> f(bind(&Foo::f1, &s, arg1, arg2, std::move(ptr)));
    function<void()> f(bind(&Foo::f2, &s, arg1, arg2, ptr.release()));

    f();
}

调用绑定到Foo::f2(最后一个参数是原始指针)的f() 可以正常工作,但是将其绑定到Foo::f1 会导致编译错误:

test.cpp: In function ‘int main()’:
test.cpp:36:70: error: no matching function for call to ‘std::function<void()>::function(std::_Bind_helper<false, void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>), Foo*, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::unique_ptr<Foo, std::default_delete<Foo> > >::type)’
     function<void()> f(bind(&Foo::f1, &s, arg1, arg2, std::move(ptr)));
                                                                      ^
test.cpp:36:70: note: candidates are:
In file included from test.cpp:2:0:
/usr/include/c++/4.8.2/functional:2251:2: note: template<class _Functor, class> std::function<_Res(_ArgTypes ...)>::function(_Functor)
  function(_Functor);
  ^
/usr/include/c++/4.8.2/functional:2251:2: note:   template argument deduction/substitution failed:
/usr/include/c++/4.8.2/functional:2226:7: note: std::function<_Res(_ArgTypes ...)>::function(std::function<_Res(_ArgTypes ...)>&&) [with _Res = void; _ArgTypes = {}]
       function(function&& __x) : _Function_base()
       ^
/usr/include/c++/4.8.2/functional:2226:7: note:   no known conversion for argument 1 from ‘std::_Bind_helper<false, void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>), Foo*, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::unique_ptr<Foo, std::default_delete<Foo> > >::type {aka std::_Bind<std::_Mem_fn<void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>)>(Foo*, std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>)>}’ to ‘std::function<void()>&&’
/usr/include/c++/4.8.2/functional:2429:5: note: std::function<_Res(_ArgTypes ...)>::function(const std::function<_Res(_ArgTypes ...)>&) [with _Res = void; _ArgTypes = {}]
     function<_Res(_ArgTypes...)>::
     ^
/usr/include/c++/4.8.2/functional:2429:5: note:   no known conversion for argument 1 from ‘std::_Bind_helper<false, void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>), Foo*, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::unique_ptr<Foo, std::default_delete<Foo> > >::type {aka std::_Bind<std::_Mem_fn<void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>)>(Foo*, std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>)>}’ to ‘const std::function<void()>&’
/usr/include/c++/4.8.2/functional:2206:7: note: std::function<_Res(_ArgTypes ...)>::function(std::nullptr_t) [with _Res = void; _ArgTypes = {}; std::nullptr_t = std::nullptr_t]
       function(nullptr_t) noexcept
       ^
/usr/include/c++/4.8.2/functional:2206:7: note:   no known conversion for argument 1 from ‘std::_Bind_helper<false, void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>), Foo*, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::unique_ptr<Foo, std::default_delete<Foo> > >::type {aka std::_Bind<std::_Mem_fn<void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>)>(Foo*, std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>)>}’ to ‘std::nullptr_t’
/usr/include/c++/4.8.2/functional:2199:7: note: std::function<_Res(_ArgTypes ...)>::function() [with _Res = void; _ArgTypes = {}]
       function() noexcept
       ^
/usr/include/c++/4.8.2/functional:2199:7: note:   candidate expects 0 arguments, 1 provided

我做错了什么?

我正在使用 gcc 4.8.2 和 -std=c++0x-std=c++11 也失败)标志。

【问题讨论】:

  • 将右值传递给 std::bind 存在问题,请参阅stackoverflow.com/questions/4871273/… f1 真的需要获取指针的所有权吗? f1 可以通过 const ref 获取指针,然后您可以使用 std::ref coliru.stacked-crooked.com/a/5ebe39ccca544bea 将指针传递给 std::bind
  • @justinls 是的,它需要拥有所有权,但感谢您的示例。如果我找不到使用 std::move 使其工作的方法,我将使用您的建议提出解决方法
  • 如果你的函数没有对你的唯一指针 (std::unique_ptr &&) 进行右值引用,为什么还要使用 std::move ?

标签: c++ c++11


【解决方案1】:

其他答案中描述的绑定问题(截至撰写本文时)并不是编译器在问题中抱怨的问题。问题是std::function 必须是 CopyConstructible,这要求它的参数(将由函数存储)也是 CopyConstructible。

来自标准【20.9.11.2 类模板函数】

template<class F> function(F f); 
template <class F, class A> function(allocator_arg_t, const A& a, F f);

要求:F 应为 CopyConstructible。 f 对于参数类型 ArgTypes 和返回类型 R 应是 Callable (20.9.11.2)。 A的拷贝构造函数和析构函数都不会抛出异常...

考虑这个甚至不包含绑定的例子:

#include <functional>
#include <memory>

using namespace std;

struct NonCopyableFunctor {
  NonCopyableFunctor(){}
  NonCopyableFunctor(const NonCopyableFunctor &) = delete;
  NonCopyableFunctor(NonCopyableFunctor &&){}
  void operator()(){}
};

int main() 
{
  NonCopyableFunctor fun;
  function<void()> vfun(move(fun)); // even though I move here,
  // it still complains about a copy going on elsewhere.
}

这是 clang 的输出:

[orm@localhost ~]$ clang++ -std=c++11 bound_unique.cc 
In file included from bound_unique.cc:1:
/usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.2/../../../../include/c++/4.8.2/functional:1911:10: error: call to deleted constructor of 'NonCopyableFunctor'
            new _Functor(*__source._M_access<_Functor*>());
                ^        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.2/../../../../include/c++/4.8.2/functional:1946:8: note: in instantiation of member function
      'std::_Function_base::_Base_manager<NonCopyableFunctor>::_M_clone' requested here
              _M_clone(__dest, __source, _Local_storage());
              ^
/usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.2/../../../../include/c++/4.8.2/functional:2453:33: note: in instantiation of member function
      'std::_Function_base::_Base_manager<NonCopyableFunctor>::_M_manager' requested here
            _M_manager = &_My_handler::_M_manager;
                                       ^
bound_unique.cc:16:20: note: in instantiation of function template specialization 'std::function<void ()>::function<NonCopyableFunctor, void>' requested here
  function<void()> vfun(move(fun));
                   ^
bound_unique.cc:8:3: note: function has been explicitly marked deleted here
  NonCopyableFunctor(const NonCopyableFunctor &) = delete;
  ^
1 error generated.

请注意,如果您绑定 unique_ptr,则生成的绑定对象将是不可复制的。 Bind 仍然会编译。

【讨论】:

    【解决方案2】:

    1) 以下代码无法编译

     function<void()> f(bind(&Foo::f1, &s, arg1, arg2, std::move(ptr)));
     // just define f, not even call it
    

    因为function 要求可调用对象是可复制构造的,但是当bind 采用像unique_ptr 这样的不可复制参数时,返回的仿函数将是不可复制的,如其他答案中所述。

    2) 所以不要将function 用于bind。但是下面的代码也不会编译

     auto f(bind(&Foo::f1, &s, arg1, arg2, std::move(ptr))); // a
     f();                                                    // b
    

    因为在步骤 (a) bind 将您提供给它的内容存储为左值(reference_wrapper 除外),并在步骤 (b) 将其传递给内部仿函数。因此它要求绑定的参数是可复制的,因为这些参数是按值传递的,而不是引用传递。

    3) 然后尝试使用原始指针。但是下面的代码也不会编译

     auto f(bind(&Foo::f1, &s, arg1, arg2, ptr.release()));
     f();
    

    类似(2)的原因,functor 存储了一个int*,并在调用时尝试将其转换为参数类型unique_ptr&lt;int&gt;。但是构造函数unique_ptr(pointer p)explicit


    要编译它,你需要一个这样的函数

    void f3(string s1, string s2, unique_ptr<Foo>& p)
    //                                           ^ use reference; add const if you need
    {
        printf("3: %s %s %s\n", s1.c_str(), s2.c_str(), p->str());
    }
    
    auto f(bind(&Foo::f3, &s, arg1, arg2, std::move(ptr)));
    f();
    

    注意f可以被多次调用,参数p引用的unique_ptrbind的返回对象中存储的unique_ptr相同。

    【讨论】:

      【解决方案3】:

      嗯,在处理 r 值引用时,std::bind 似乎确实有问题。一种替代方法是使用 lambda 函数:

      function<void()> f([&]() { s.f1(arg1,arg2,std::move(ptr)); });
      

      为了使其工作,您还必须更改 f1 的签名,使其接受 unique_ptr 作为 r 值引用:

      void f1(string s1, string s2, unique_ptr<Foo>&& p)
      

      (即使 std::bind 可以处理 r-value 引用,您仍然必须这样做,因为 std::unique_ptr 没有复制构造函数,只有移动构造函数可以访问!)

      但是请注意,您的构造相当危险(如果 std::bind 可以工作):如果您调用 f() 两次,您最终会遇到运行时异常。

      【讨论】:

      • 请注意,在 C++11 中,您无法在这样的 lambda 表达式中轻松捕获 unique_ptr。如果你尝试这个,你会在运行时得到一个空指针异常,如果你在原始unique_ptr 超出范围后调用该函数。有关一些解决方法,请参阅 this question
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多