Asynchrone Callbacks/Buffer-Ownership
-
Hallo.
Angenommen, ich kann ein paar asynchrone Eingabe-/Ausgabe-Tasks durchführen, welche Daten aus einem Puffer (Array) lesen bzw. schreiben, die dann einen Callback aufrufen, wenn sie fertig sind.
Während die Operation noch läuft, müssen die Puffer gültig bleiben. Beispiel:void callback(buffer& b) { ... } // wird irgendwann in anderem Thread aufgerufen void foo() { buffer b; start_operation(callback, std::move(b)); // move gut?Wie sollte der Besitz von b hier geregelt werden?
Möglichkeiten, die mir einfallen, sind:1. Nutzer erstellt Puffer und behält Ownership auch während der Operation, muss also gewährleisten, dass der Puffer gültig bleibt.
Der Vorteil, den ich hier denke ich bekomme, ist, dass der Nutzer den Puffer befüllen und manipulieren kann und er nicht kopiert werden muss, da die Operation dann quasi nur eine Referenz/Zeiger auf den Puffer hat.
Nachteil: Der Nutzer muss daran denken, alles richtig zu machen. Fatal wäre dann z.B.void foo() { buffer b; // auf dem Stack! start_operation(callback, b); // call by reference für b } // b.~buffer() :(Bessere Lösung wäre wohl
void callback(buffer*); void foo() { auto bptr = make_unique<buffer>(); // wegen Exceptions etc. start_operation(callback, bptr.get()); bptr.release(); // damit buffer gültig bleibt, kann aber leicht vergessen werden :( } void callback(buffer* b) { unique_ptr<buffer> bptr(b); // wieder Exception-safety + RAII ... }So mache ich es momentan, hier kann aber so viel vergessen werden. Aber das ist so viel manuelle Speicherverwaltung. Deswegen suche ich Alternativen.
2. Nutzer hat niemals Besitz am Puffer und die Library kümmert sich drum, also
void callback(buffer& b) { ... } // nur Referenz, nachdem callback durch ist wird buffer gelöscht void foo() { start_operation(callback, 50); // 50 Bytes Puffer }Vorteil: Viel weniger manuelle Verwaltung.
Nachteil: Nutzer muss Puffer im Callback manuell kopieren, wenn er mit den Daten noch etwas anfangen will.3. Besitz hin und her reichen:
void callback(buffer b) { ... } void foo() { buffer b; start_operation(callback, std::move(b)); // irgendwann wird dann callback(std::move(b)) aufgerufen }Hat das irgendwelche Nachteile?
Was würdet ihr machen?
-
Mein Vorschlag wäre:
auto b = std::make_shared<buffer>(); start_operation([b](){ // ... });Das wird so auch in den Asio Beispielen verwendet.