2014-09-04 18 views
9

Ho una funzione che opera su una grande porzione di dati passati come argomento sink. Il mio tipo BigData è già C++ 11-aware e viene fornito con il costruttore mossa completamente funzionale e spostare le implementazioni di assegnazione, così posso andare via senza dover copiare il dannato:Argomenti di sink e spostamento della semantica per le funzioni che possono fallire (sicurezza delle eccezioni)

Result processBigData(BigData); 

[...] 

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

Questo tutto funziona perfettamente bene. Tuttavia, la mia funzione di elaborazione potrebbe non riuscire occasionalmente in fase di runtime con conseguente eccezione. Questo non è davvero un problema, dato che posso solo risolvere roba e riprovare:

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

Naturalmente, questo non funzionerà.

Poiché I si è spostato i miei dati nella funzione di elaborazione, al momento dell'arrivo nel gestore di eccezioni, b non sarà più utilizzabile.

Questo minaccia di ridurre drasticamente il mio entusiasmo per il passaggio di argomenti inaffondabili.

Quindi, ecco la domanda: come affrontare una situazione come questa nel moderno codice C++? Come recuperare l'accesso ai dati precedentemente spostati in una funzione che non è riuscita a eseguire?

È possibile modificare l'implementazione e le interfacce per entrambi BigData e processBigData come si desidera. La soluzione finale dovrebbe tuttavia cercare di minimizzare gli inconvenienti rispetto al codice originale per quanto riguarda l'efficienza e l'usabilità.

+0

Domanda importante, il risultato contiene le risorse spostate di b o si basa solo su di esso? – IdeaHat

+0

@MadScienceDreams Il 'Result' è calcolato da' b', esso _non_ contiene un riferimento a, o una copia dell'originale 'b'. – ComicSansMS

+0

@ComicSansMS Ma contiene i contenuti spostati (al contrario di quelli copiati)? – Potatoswatter

risposta

2

Sono ugualmente non contento di questo problema.

Per quanto posso dire, il miglior linguaggio corrente è quello di dividere il valore di pass-by in una coppia di riferimenti pass-by.

template< typename t > 
std::decay_t<t> 
val(t && o) // Given an object, return a new object "val"ue by move or copy 
    { return std::forward<t>(o); } 

Result processBigData(BigData && in_rref) { 
    // implementation 
} 

Result processBigData(BigData const & in_cref) { 
    return processBigData(val(in_cref)); 
} 

Ovviamente, i bit e le parti dell'argomento potrebbero essere stati spostati prima dell'eccezione. Il problema si estende a tutte le chiamate processBigData.

Ho avuto un'ispirazione per sviluppare un oggetto che si riporta alla sua origine su alcune eccezioni, ma questa è una soluzione ad un particolare problema all'orizzonte in uno dei miei progetti. Potrebbe finire per essere troppo specializzato, o potrebbe non essere affatto fattibile.

+0

Non sono ancora sicuro del motivo per cui si desidera passare un RHR per la funzione se non consuma effettivamente BigData. .. Immagino che gli permetterò di consumarlo in futuro? Inoltre, cosa fai nel caso di un tipo non copiabile (come 'std :: unique_ptr')? – IdeaHat

+0

Sì, l'aggiunta di un sovraccarico per i valori di riferimento avrebbe davvero aiutato qui. È possibile rinviare lo spostamento da "BigData" a un punto in cui è possibile garantire che non si verificherà alcuna eccezione. Ovviamente questo soffre dei soliti inconvenienti di dover introdurre sovraccarichi di rvalue-ref su interfacce di funzione (quindi non sono convinto al 100% che soddisfi il mio vincolo di usabilità), ma potrebbe effettivamente funzionare bene in determinate situazioni. +1 in entrambi i casi. – ComicSansMS

+0

@MadScienceDreams 1. Sì, dati i commenti chiarificatori sotto la domanda, non sono sicuro del motivo per cui non è un 'const &' in primo luogo, ma ho appena risposto concettualmente alla domanda. 2. I tipi non copiabili non avrebbero bisogno di 'const &' overload, tutto qui. – Potatoswatter

2

Apparentemente questo problema è stato discusso vivacemente al recente CppCon 2014. Herb Sutter ha riassunto l'ultimo stato delle cose nel suo discorso conclusivo, Back to the Basics! Essentials of Modern C++ Style (slides).

La sua conclusione è piuttosto semplice: Non utilizzare il pass-by-value per gli argomenti sink.

Gli argomenti per l'utilizzo di questa tecnica in primo luogo (come reso popolare dal keynote di Meeting C++ 2013 di Eric Niebler C++11 Library design (slides)) sembrano essere superati dagli svantaggi. La motivazione iniziale per il passaggio degli argomenti del sink sink è stata quella di eliminare l'esplosione combinatoria per sovraccarico di funzione risultante dall'uso di const&/&&.

Sfortunatamente, sembra che questo porti a un numero di conseguenze indesiderate. Uno dei quali è un potenziale svantaggio dell'efficienza (principalmente a causa di allocazioni di buffer non necessarie). L'altro è il problema con la sicurezza delle eccezioni da questa domanda. Entrambi sono discussi nel discorso di Herb.

La conclusione di Herb è quello di non uso pass-by-value per gli argomenti lavandino, ma invece contare su separato const&/&& (con const& essendo il difetto ed && riservato a quei pochi casi in cui è richiesta l'ottimizzazione).

Questo corrisponde anche a quello suggerito da @Potatoswatter's answer. Passando l'argomento sink via && potremmo essere in grado di posticipare lo spostamento effettivo dei dati dall'argomento a un punto in cui possiamo dare una garanzia noexcept.

Mi è piaciuta l'idea di passare gli argomenti del sink in base al valore, ma sembra che non regga in pratica come tutti speravano.