2014-09-04 11 views
9

Ich habe eine Funktion, die auf einem großen Teil der Daten arbeitet als ein Sink-Argument übergeben. Mein BigData Typ ist bereits C++ 11-aware und kommt mit voll funktionsfähigem bewegen Konstruktor und Zuweisungs Implementierungen bewegen, so kann ich weg, ohne das verdammte Ding zu kopieren:Sink-Argumente und Verschieben Semantik für Funktionen, die fehlschlagen können (starke Ausnahme Sicherheit)

Result processBigData(BigData); 

[...] 

BigData b = retrieveData(); 
Result r = processBigData(std::move(b)); 

Das alles perfekt funktioniert gut. Meine Verarbeitungsfunktion kann jedoch gelegentlich zur Laufzeit fehlschlagen, was zu einer Ausnahme führt. Dies ist nicht wirklich ein Problem, da ich nur Sachen reparieren und wiederholen kann:

BigData b = retrieveData(); 
Result r; 
try { 
    r = processBigData(std::move(b)); 
} catch(std::runtime_error&) { 
    r = fixEnvironmnentAndTryAgain(b); 
    // wait, something isn't right here... 
} 

Natürlich wird dies nicht funktionieren.

Seit ich meine Daten in die Verarbeitungsfunktion verschoben haben, werde ich b nicht mehr verwendbar sein, wenn ich in der Ausnahmebehandler ankommen.

Dies droht meine Begeisterung für das Übergeben von Sinkargumenten nach Wert zu reduzieren.

Also hier ist die Frage: Wie mit einer Situation wie dieser in modernen C++ - Code umgehen? Wie kann der Zugriff auf Daten abgerufen werden, die zuvor in eine Funktion verschoben wurden, die nicht ausgeführt werden konnte?

Sie können die Implementierung und Schnittstellen für beide BigData und processBigData ändern, wie Sie möchten. Die endgültige Lösung sollte jedoch versuchen, Nachteile gegenüber dem ursprünglichen Code in Bezug auf Effizienz und Benutzerfreundlichkeit zu minimieren.

+0

Wichtige Frage, enthält Ergebnis die verschobenen Ressourcen von b oder basiert nur darauf? – IdeaHat

+0

@MadScienceDreams Das 'Ergebnis' wird nur aus' b' berechnet, es enthält _nicht_ einen Verweis auf oder eine Kopie des Originals 'b'. – ComicSansMS

+0

@ComicSansMS Aber enthält es die verschobenen (im Gegensatz zu kopierten) Inhalte? – Potatoswatter

Antwort

2

Ich bin in ähnlicher Weise von diesem Problem verblüfft.

Soweit ich sagen kann, ist die beste aktuelle Idiom, den Pass-by-Wert in ein Paar von Pass-by-Referenzen zu teilen.

Natürlich könnten Teile des Arguments vor der Ausnahme verschoben worden sein. Das Problem breitet sich auf alle Anrufe aus.

Ich hatte eine Inspiration, ein Objekt zu entwickeln, das sich bei bestimmten Ausnahmen zurück zur Quelle bewegt, aber das ist eine Lösung für ein bestimmtes Problem am Horizont in einem meiner Projekte. Es könnte am Ende zu spezialisiert oder gar nicht machbar sein.

+0

Ich bin mir immer noch nicht sicher, warum Sie ein RHR für die Funktion übergeben möchten, wenn es BigData nicht wirklich verbraucht. .. ich denke, es in Zukunft zu konsumieren? Was tun Sie auch bei einem nicht kopierbaren Typ (wie 'std :: unique_ptr')? – IdeaHat

+0

Ja, eine Überladung für Rvalue Refs würde in der Tat hier helfen. Sie können das Verschieben von 'BigData' auf einen Punkt verschieben, an dem Sie garantieren können, dass keine Ausnahme auftritt. Natürlich leidet dies unter den üblichen Nachteilen der Notwendigkeit, rvalue-ref-Überladungen auf Funktionsschnittstellen einzuführen (daher bin ich nicht 100% überzeugt, dass es meine Benutzbarkeitsbeschränkung erfüllt), aber es könnte in bestimmten Situationen tatsächlich gut funktionieren. +1 in beide Richtungen. – ComicSansMS

+0

@MadScienceDreams 1. Ja, angesichts der Klärung Kommentare unter der Frage, ich bin mir nicht sicher, warum es nicht ein "const &" in erster Linie, aber ich habe nur die Frage konzeptionell beantwortet. 2. Nicht kopierbare Typen benötigen nicht die Überladung "const &", das ist alles. – Potatoswatter

2

Anscheinend wurde dieses Thema auf der letzten CppCon 2014 lebhaft diskutiert. Herb Sutter fasste den letzten Stand der Dinge in seinem Abschlussvortrag, Back to the Basics! Essentials of Modern C++ Style (slides) zusammen.

Seine Schlussfolgerung ist ganz einfach: Verwenden Sie nicht Pass-by-Wert für Sink-Argumente.

Die Argumente für die Verwendung dieser Technik an erster Stelle (wie von Eric Niebler Meeting C++ 2013 Keynote C++11 Library design (slides) popularisiert) scheinen durch die Nachteile aufgewogen werden. Die anfängliche Motivation für das Übergeben von Sink-Argumenten als Wert war, die kombinatorische Explosion für Funktionsüberlastungen, die sich aus der Verwendung von const&/&& ergibt, loszuwerden.

Leider scheint es, dass dies eine Reihe von unbeabsichtigten Folgen hat. Eines davon sind potenzielle Effizienznachteile (hauptsächlich aufgrund unnötiger Pufferzuweisungen). Der andere ist das Problem mit Ausnahme Sicherheit von dieser Frage. Beides wird in Herbs Vortrag besprochen.

Herb Schlussfolgerung ist zu nicht Verwendung Pass-by-Wert für sink Argumente, sondern verlassen sich auf separaten const&/&& (mit const& ist die Standardeinstellung und && für die wenigen Fällen vorbehalten, in denen Optimierung erforderlich ist).

Dies entspricht auch, was @Potatoswatter's answer vorgeschlagen. Indem wir das Sink-Argument über && übergeben, können wir möglicherweise die tatsächliche Verschiebung der Daten von dem Argument zu einem Punkt verschieben, an dem wir keine Garantie geben können.

Ich mochte irgendwie die Idee, die Argumente von Sinks als Wert zu übergeben, aber es scheint, dass es in der Praxis nicht so gut funktioniert, wie alle gehofft haben.