Type erasure / Asynchroner Callback



  • Hi!
    Ich habe folgendes Szenario: Ein Puffer wird asynchron mit Daten befüllt. Sobald die Operation fertig ist, soll ein Callback in irgendeinem Thread aufgerufen werden, der als Parameter den Puffer enthält. Es versteht sich von selbst, dass der Puffer während der Operation gültig bleiben muss, Stack-Speicher entfällt also.

    Jetzt will ich natürlich jede Form von manueller Speicherverwaltung vermeiden, weshalb ich mir sowas vorstelle:

    void callback(buffer);
    
    vector<uint8_t> buffer(1024);
    start_operation(move(buffer), callback); // user muss sich nicht mehr um Lifetime von buffer kümmern
    
    /* ... */
    // irgendwo in der Library, wenn die Operation fertig ist:
    callback(move(buffer)); // gib Puffer an Nutzer zurück (per Move-Konstruktor)
    
    void callback(buffer b) { /* ... */ }
    

    Mein Problem: wie bekomme ich das generisch (mit Templates/allgemeinen Typen) hin? Wenn die Operation abgeschlossen ist, habe ich prinipiell nur einen Pointer auf den Puffer (bzw. auf ein Objekt, welches den Puffer dann beinhaltet), der Typ ist verloren. Die Typen-Information rette ich mir mit virtual-Funktionen, also dispatche damit unterschiedliche Operations-Typen (lesen/schreiben/abbrechen etc.). Aber kann ich auch irgendwie das Typen-System von C++ benutzen, um die Template-Parameter mitzunehmen? Wenn ich das alles nicht manuell machen will, fällt mir spontan nur noch RTTI, also typeid ein.

    Hat jemand einen Tipp?



  • Etwas ausführlicheres Beispiel:

    void start_read_operation(buffer&& b, callback_function callback) {
        unique_ptr<operation> new_operation(new read_operation(b, callback)); // Polymorph!
        new_operation->start();
        new_operation.release();
    }
    
    // Callback-Thread:
    void callback_thread() {
       while(1) {
          operation* op = wait_for_operation_to_complete();
          unique_ptr<operation> op_ptr(op); // RAII
          op->callback(std::move(op->buffer)); // Hier weiß ich nicht mehr, ob es eine read/write/whatever Operation ist, aber callback ist virtuell
       }
    }
    

    Jetzt will ich aber ein Funktionstemplate benutzen:

    template <typename BufferType>
    void start_read_operation(BufferType&& buffer, callback_function callback) { /* ... */ }
    

    Und genau da hapert's, da ich den Typ irgendwie dynamisch kodieren muss.



  • Kannste nicht beim virtual Dispatch in operation nicht direkt start_read_operation aufrufen?



  • Nathan schrieb:

    Kannste nicht beim virtual Dispatch in operation nicht direkt start_read_operation aufrufen?

    Wie meinst du das? operation::callback wird erst aufgerufen, nachdem irgendwann die Operation fertig ist, die mit start_read_operation() begonnen wurde.

    Die Funktion wait_for_operation_to_complete() wartet eben so lange, bis irgendeine Operation fertig ist (egal ob read/write/bla). Aber an der Stelle kenne ich den Typ der Operation nicht mehr, deshalb virtual. Aber jetzt müsste operation ja auch ein Template werden, um den Buffer-Type zu generalisieren. Nur wie rette ich diesen Typen über die Laufzeit?



  • class operation
    {
    public:
        virtual void invoke_callback() = 0;
        // ...
    };
    
    template <class BufferType, class CallbackType>
    class operation_impl : public operation
    {
    public:
        operation_impl(BufferType&& buffer, CallbackType&& callback)
            : m_buffer(move(buffer)),
            m_callback(move(callback))
        {
        }
    
        virtual void invoke_callback()
        {
            m_callback(move(m_buffer));
        }
    
    private:
        CallbackType m_callback;
        BufferType m_buffer;
    };
    
    template <class BufferType, class CallbackType>
    class read_operation : public operation_impl<BufferType, CallbackType>
    {
    public:
        // ...
    };
    
    void callback_thread()
    { 
        while (true)
        { 
           unique_ptr<operation> op(wait_for_operation_to_complete());
           op->invoke_callback();
        } 
    }
    

    ?



  • Wow danke, das probiere ich mal aus.



  • Warum benutzt du eine Universal-Referenz für das Callback-Objekt? Kann man bei Sachen wie Funktionszeigern/Funktoren etc. nicht davon ausgehen, dass die billig zu kopieren sind?



  • Oops, war ein Fehler.
    Ist auch keine Universal-Reference sondern eine R-Value Reference (ist ja kein Funktionstemplate).

    Mach einfach CallbackType const& draus.



  • Okay, es funktioniert wunderbar! Danke nochmal.

    Edit: Nicht vergessen, einen virtual-Destruktor hinzuzufügen!


Anmelden zum Antworten