std::bind - Warum wird ein Kopier-Konstruktor benötigt?
-
Th69 schrieb:
Stichwort zum Suchen: copy elision, s. z.B. Copy constructor elision
Durch das manuelle Hinzufügen des Move-Konstruktor wird der Copy-Konstruktor nicht mehr automatisch erzeugt. Er wird zwar bei der "copy elision" nicht aufgerufen, muß aber trotzdem existieren.
Blödsinn.
Das Problem besteht darin, dass eine Funktion instantiiert wird, die nie aufgerufen wird:
// Clone a function object that is not location-invariant or // that cannot fit into an _Any_data structure. static void _M_clone(_Any_data& __dest, const _Any_data& __source, false_type) { __dest._M_access<_Functor*>() = new _Functor(*__source._M_access<_Functor*>()); }Die Instantiierung wird durch ein
switch-Statement bedingt, das in der Ausführung eine anderen Pfad einschlägt. Siehe Zeile 1666:static bool _M_manager(_Any_data& __dest, const _Any_data& __source, _Manager_operation __op) { switch (__op) { #if __cpp_rtti case __get_type_info: __dest._M_access<const type_info*>() = &typeid(_Functor); break; #endif case __get_functor_ptr: __dest._M_access<_Functor*>() = _M_get_pointer(__source); break; case __clone_functor: _M_clone(__dest, __source, _Local_storage()); // <=== Hier break; case __destroy_functor: _M_destroy(__dest, _Local_storage()); break; } return false; }Wenn wir die markierte Zeile auskommentieren, funktioniert dein Beispiel auch mit als deleted definiertem Kopierkonstruktor. libc++ hingegen verwendet copy&swap im perfect forwarding assignment operator.
-
Mit einem aktuellen Compiler gibt es keinen Grund mehr, bind zu benutzen:
f = [bar=move(bar)]{ do_something(bar); };
-
@Arcoth
Danke für die Erklärung, verstehe jetzt das Problem
Ärgerlich ist dabei eben, dass es anscheinend nur funktioniert, wenn die Klasse auch einen Kopierkonstruktor definiert hat.@manni66
Interessante Lösung, kommt aber auf das Gleiche hinaus (der Compiler meldet ebenfalls einen fehlenden Kopierkonstruktor).
-
Arcoth schrieb:
libc++ hingegen verwendet copy&swap im perfect forwarding assignment operator.
Heißt? Der Code compiliert bei mir jedenfalls weder mit GCC noch mit Clang (auch nicht mit libc++).
Der Grund ist eigentlich auch klar, wobei ich die Erklärung von Arcoth etwas knapp finde. Die Funktion die instanziert aber niemals aufgerufen wird ist nicht von
std::bindsondern vom Assignment Operator vonstd::function. Dazu muss man Wissen, dassstd::functioneine Technik namens Type Erasure nutzt um Objekte beliebigen Typs zu speichern, solange er sich an ein bestimmtes Interface hält. Also beistd::functioneben etwas aufrufen, dass aussieht wie eine Funktion. Allerdings soll manstd::functionnatürlich auch kopieren können weshalb das Type Erasure Interface auch eine Funktion zum Kopieren anbieten muss. Diese Funktion kann aber keine Template Funktion sein weshalb es Compiler Fehler hagelt, auch wenn man niemals versucht diese Funktion aufzurufen. Mir fällt gerade auch keine Möglichkeit ein wie man std::function implementieren soll ohne diese Einschränkung. Du wirst also wohl damit leben müssen, dass deine Function Objekt kopierbar sein müssen.
-
shft schrieb:
@manni66
Interessante Lösung, kommt aber auf das Gleiche hinaus (der Compiler meldet ebenfalls einen fehlenden Kopierkonstruktor).Naja, function ist so ja immer noch vorhanden. Es war auch nur als Hinweis gedacht, dass bind nicht mehr benötigt wird.
Komplettlösung ohne function
#include <iostream> using namespace std; class foo { public: foo() { cout << "Konstruktor\n"; } //foo( const foo& k ) :member{ k.member } { cout << "Kopier-Konstruktor\n"; } foo( foo&& m ) :member{ m.member } { cout << "Move-Konstruktor\n"; } ~foo() { cout << "Destruktor\n"; } int member{ 42 }; }; void do_something( const foo& f ) { cout << f.member << endl; } int main() { auto f = [] { foo bar; bar.member = 1337; return [bar=move(bar)]{ do_something(bar); }; }; f(); }Ist jetzt natürlich ein sehr künstliches Problem ...
-
shft schrieb:
@manni66
Interessante Lösung, kommt aber auf das Gleiche hinaus (der Compiler meldet ebenfalls einen fehlenden Kopierkonstruktor).Klar, der Member ist schließlich, genau wie im vorigen Beispiel, ein Objekt vom Typ
foo, und es wird fälschlicherweise der Kopierkonstruktor des closure Typen benötigt.Es gibt keine direkte Lösung für dein Problem - außer, nicht mehr
functionzu verwenden, oder aber die Lebenszeit des Objekts zu analysieren und nur einen reference wrapper zu überreichen, damitbindlediglich eine Referenz auf das Objekt im Proxy speichert:bind(do_something, std::ref(bar))funktioniert in deinem Beispiel.
-
Man könnte auch einfacher sagen: da
std::functionkopierbar ist, muss auch alles was man in einstd::functionreintut kopierbar sein.Ob das
std::functionObjekt dann jemals kopiert wird spielt dabei keine Rolle, dastd::functionType-HidingType-Erasure betreibt, und daher bei nicht-kopierbaren Objekten nur die Wahl hat:- Initialisierung des
std::functionkompiliert nicht. - Initialisierung des
std::functionkompiliert, das so erstelltestd::functionwirft aber beim Versuch eine Kopie zu erstellen eine Exveption.
(2) wäre natürlich sehr doof, daher (1).
Und natürlich kann man noch "genereller" werden: Der C++ Standard geht generell davon aus dass Funktoren kopierbar sind. Mehr noch: er geht davon aus dass Funktoren relativ billig kopierbar sind. Erkennbar daran dass die std. Algorithmen (
sortetc.) den Funktor "by value" nehmen.
- Initialisierung des
-
sebi707 schrieb:
Arcoth schrieb:
libc++ hingegen verwendet copy&swap im perfect forwarding assignment operator.
Heißt?
Dass es nicht funktioniert? Ich wollte mit "hingegen" nur darauf hinweisen, dass die Erklärung hier ziemlich trivial ist.
-
Arcoth schrieb:
Es gibt keine direkte Lösung für dein Problem - außer, nicht mehr
functionzu verwenden, oder aber die Lebenszeit des Objekts zu analysieren und nur einen reference wrapper zu überreichen, damitbindlediglich eine Referenz auf das Objekt im Proxy speichert:bind(do_something, std::ref(bar))funktioniert in deinem Beispiel.Man könnte auch nen
shared_ptr<foo>in den Funktor reinstecken. So lange der Funktor nur lesend (const) auffoozugreift sollte das kein Problem machen.
-
Ich würde sagen, es liegt an der Type Erasure von std::function. std::function muss kopierbar sein. Damit std::function kopierbar ist, müssen auch alle Funktionsobjekte, die so gewrappt werden, kopierbar sein. Da ist es ganz egal, ob du ein std::function Objekt tatsächlich kopierst oder nicht. Die Möglichkeit zum Kopieren muss gewährleistet sein, damit man später ein std::function kopieren kann. Alternativ könnte man auch nicht-kopierbare Funktionsobjekte wrappen. Dann müsste man aber beim Versuch ein solches std::function zu kopieren, eine Ausnahme oder so etwas werfen. Warum? Type Erasure:
template<class Signature> class abstract_callable; template<class Return, class...Args> class abstract_callable<Return(Args...)> { public: virtual ~abstract_callable() = default; virtual abstract_callable* clone() const = 0; // <-- ding ding ding! virtual Return invoke(Args...args) = 0; }; template<class Signature> class function; template<class Return, class...Args> class function<Return(Args...)> { unique_ptr<abstract_callable<Return(Args...)>> funptr; public: ... function(function const& x) : funptr(x.funptr ? x.funptr->clone() : nullptr) {} ... };(Skizze einer möglichen std::function Implementierung)
An der Stelle, wo ein konkretes callable-Objekt erzeugt wird, was von abstract_callable ableitet muss man natürlich auch
clone()implementieren, ob es benutzt wird oder nicht.
-
Das ist ziemlich genau das was ich schon geschrieben habe. Bloss dass ich statt Type Erasure fälschlicherweise "Type Hiding" geschrieben hatte (korrigiert).
-
Super. Jetzt haben wir 4 Erklärungen für das gleiche Problem

-
*verschoben*
-
hustbaer schrieb:
*verschoben*
Na das machst du doch prima! Warum willste nicht gleich Mod werden?