2013-11-28 8 views
8

Ich versuche, den folgenden Code funktioniert:Binding Funktionen mit unique_ptr Argumente std :: function <void()>

#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(); 
} 

Aufruf f(), gebunden an Foo :: f2 (letzte Parameter ist ein Rohzeiger) funktioniert gut, aber die Bindung an Foo :: f1 verursacht Kompilierung Fehler:

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 

Was mache ich falsch?

Ich benutze gcc 4.8.2 und -std = C++ 0x (-std = C++ 11 fehlschlägt auch) Flags.

Dank

+1

Es gibt Probleme mit der Weitergabe von rvalues ​​an std :: bind, siehe http://stackoverflow.com/questions/4871273/passing-rvalues-through-stdbind Benötigt f1 wirklich Besitz von dem Zeiger? f1 könnte den Zeiger durch const ref nehmen und dann können Sie den Zeiger auf std :: bind übergeben mit std :: ref http://coliru.stacked-crooked.com/a/5ebe39ccca544bea – countfromzero

+1

Siehe http://stackoverflow.com/ Fragen/9955714/does-stdbind-arbeiten-mit-bewegen-nur-types-in-general-und-stdunique-ptr-in-part – zch

+0

@justinls Yup, muss es Besitz übernehmen, aber danke für Ihr Beispiel. Wenn ich keinen Weg finden kann, um es mit std :: move funktionieren zu lassen, schlage ich eine Umgehung mit Ihrem Vorschlag vor. – rogerzanoni

Antwort

3

Hmm es scheint wirklich, dass std :: bind Probleme hat, wenn sie mit r-Wert Referenzen handelt. Eine Alternative wäre, eine Lambda-Funktion zu verwenden:

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

Damit dies funktioniert müssen Sie auch die Unterschrift von f1 ändern, dass sie die unique_ptr als r-Referenzwert akzeptiert:

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

(Auch wenn std :: binden könnte r-Wert Referenzen behandeln, würden Sie immer noch, dies zu tun haben, weil std :: unique_ptr keine Kopie Konstruktor hat, nur der Umzug Konstruktor ist zugänglich!)

Hinweis dass Ihr Konstrukt ziemlich gefährlich ist (auch wenn std :: bind funktionieren würde): i Wenn Sie f() zweimal aufrufen, erhalten Sie eine Laufzeitausnahme.

+0

Beachten Sie, dass Sie in C++ 11 nicht einfach ein 'unique_ptr' in einem Lambda-Ausdruck wie diesem erfassen können. Wenn Sie dies versuchen, erhalten Sie zur Laufzeit eine Nullzeiger-Ausnahme, wenn Sie die Funktion aufrufen, sobald der ursprüngliche 'unique_ptr' den Gültigkeitsbereich verlassen hat. Für einige Problemumgehungen siehe [diese Frage] (http://stackoverflow.com/questions/8236521/how-to-capture-a-unique-ptr-into-a-lambda-expression). – Malvineous

8

Die Probleme mit Bind, die in den anderen Antworten beschrieben werden (zum Zeitpunkt des Schreibens), sind nicht das, worüber sich der Compiler in der Frage beschwert. Das Problem ist, dass std::function CopyConstructible sein muss, was erfordert, dass sein Argument (das von der Funktion gespeichert wird) auch CopyConstructible sein wird.

Vom Standard [20.9.11.2 Klasse Template-Funktion]

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

Requires: F shall be CopyConstructible. f shall be Callable (20.9.11.2) for argument types ArgTypes and return type R . The copy constructor and destructor of A shall not throw exceptions...

dieses Beispiel betrachten, die nicht einmal binden enthält darin:

#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. 
} 

Hier ist die Ausgabe von Klirren :

[[email protected] ~]$ 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. 

Beachten Sie, dass das resultierende Bindeobjekt nicht kopierbar ist, wenn Sie ein unique_ptr binden. Bind wird trotzdem noch kompilieren.

1

1) Der folgende Code wird nicht

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

weil function das aufrufbare Objekt erfordert, ist kopier konstruierbar, aber wenn bind ein nicht kopierbaren Argument wie unique_ptr nimmt der zurück Funktors wird nicht kopierbaren, wie in anderen Antworten erwähnt.

2) Verwenden Sie also einfach nicht function für bind.Jedoch der folgende Code wird nicht kompiliert entweder

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

weil in Schritt (a) bind speichert, was Sie es als L-Wert geben (außer reference_wrapper) und leitet es an den internen Funktors im Schritt (b) . Daher ist es erforderlich, dass die gebundenen Argumente kopierbar sind, denn die Parameter werden nach Wert übergeben, nicht nach Referenzen.

3) Versuchen Sie dann, den rohen Zeiger zu verwenden. Allerdings wird der folgende Code nicht entweder

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

Ein ähnlicher Grund wie (2) kompilieren, die Funktors speichert ein int*, und versuchen Sie es auf den Parameter Typ zu konvertieren unique_ptr<int>, wenn sie aufgerufen. Aber der Konstruktor unique_ptr(pointer p) ist explicit.


es zu kompilieren, müssen Sie eine Funktion wie diese

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(); 

Beachten Sie, dass f mehrfach aufgerufen werden kann, und die Parameter p Referenz der gleichen unique_ptr, die in dem zurückgegebene Objekt von bind gespeichert .

Verwandte Themen