std::bind - Warum wird ein Kopier-Konstruktor benötigt?



  • 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 ...


  • Mod

    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 function zu verwenden, oder aber die Lebenszeit des Objekts zu analysieren und nur einen reference wrapper zu überreichen, damit bind lediglich 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::function kopierbar ist, muss auch alles was man in ein std::function reintut kopierbar sein.

    Ob das std::function Objekt dann jemals kopiert wird spielt dabei keine Rolle, da std::function Type-Hiding Type-Erasure betreibt, und daher bei nicht-kopierbaren Objekten nur die Wahl hat:

    1. Initialisierung des std::function kompiliert nicht.
    2. Initialisierung des std::function kompiliert, das so erstellte std::function wirft 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 ( sort etc.) den Funktor "by value" nehmen.


  • Mod

    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 function zu verwenden, oder aber die Lebenszeit des Objekts zu analysieren und nur einen reference wrapper zu überreichen, damit bind lediglich 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 ) auf foo zugreift 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*


  • Mod

    hustbaer schrieb:

    *verschoben*

    Na das machst du doch prima! Warum willste nicht gleich Mod werden?


Anmelden zum Antworten