La regola dei tre diventa Rule of Five con C ++ 11?

Quindi, dopo aver guardato questa meravigliosa lezione sui riferimenti di valore, ho pensato che ogni class avrebbe beneficiato di un tale costruttore di “move constructor“, template MyClass(T&& other) e ovviamente un “operatore di assegnazione del movimento”, template MyClass& operator=(T&& other) come fa notare Philipp nella sua risposta, se ha membri allocati dynamicmente o generalmente memorizza i puntatori. Proprio come dovresti avere un copy-ctor, un operatore di assegnazione e un distruttore se i punti menzionati prima si applicano. Pensieri?

Direi che la Regola del Tre diventa la Regola del Tre, Quattro e Cinque:

Ogni class dovrebbe definire esplicitamente una delle seguenti serie di funzioni membro speciali:

  • Nessuna
  • Destructor, copy constructor, copy assignment operator

Inoltre, ogni class che definisce esplicitamente un distruttore può definire esplicitamente un costruttore di movimento e / o un operatore di assegnazione di movimento.

Di solito, uno dei seguenti gruppi di funzioni membro speciali è ragionevole:

  • Nessuno (per molte classi semplici in cui le funzioni membro speciali generate in modo implicito sono corrette e veloci)
  • Destructor, copy constructor, copy assignment operator (in questo caso la class non sarà mobile)
  • Distruttore, costruttore di movimento, operatore di spostamento delle mosse (in questo caso la class non sarà copiabile, utile per le classi di gestione delle risorse in cui la risorsa sottostante non è copiabile)
  • Distruttore, costruttore di copia, operatore di assegnazione copia, costruttore di movimento (a causa dell’elisione della copia, non vi è un sovraccarico se l’operatore di assegnazione copia prende il suo argomento in base al valore)
  • Distruttore, costruttore di copia, operatore di assegnazione copia, costruttore di movimento, operatore di spostamento delle posizioni

Si noti che l’operatore di spostamento costruttore e spostamento assegnazione non verrà generato per una class che dichiara esplicitamente qualcuna delle altre funzioni membro speciali, che l’operatore copia costruttore e copia assegnazione non verrà generato per una class che dichiara esplicitamente un costruttore di movimento o una mossa operatore di assegnazione e che una class con un distruttore esplicitamente dichiarato e un costruttore di copie definito implicitamente o un operatore di assegnazione di copia definito implicitamente è considerata deprecata. In particolare, la seguente class di base polimorfica C ++ 03 perfettamente valida

 class C { virtual ~C() { } // allow subtype polymorphism }; 

dovrebbe essere riscritto come segue:

 class C { C(const C&) = default; // Copy constructor C(C&&) = default; // Move constructor C& operator=(const C&) & = default; // Copy assignment operator C& operator=(C&&) & = default; // Move assignment operator virtual ~C() { } // Destructor }; 

Un po ‘fastidioso, ma probabilmente migliore dell’alternativa (generazione automatica di tutte le funzioni dei membri speciali).

In contrasto con la Regola dei Tre Grandi, dove il mancato rispetto della regola può causare seri danni, non dichiarare esplicitamente il costruttore della mossa e l’operatore di assegnazione del movimento è generalmente soddisfacente ma spesso subottimale rispetto all’efficienza. Come menzionato in precedenza, gli operatori di spostamento costruttore e spostamento assegnazione vengono generati solo se non esiste un costruttore di copie esplicitamente dichiarato, un operatore di assegnazione delle copie o un distruttore. Questo non è simmetrico al comportamento tradizionale di C ++ 03 rispetto alla generazione automatica del costruttore di copie e dell’operatore di assegnazione copia, ma è molto più sicuro. Quindi la possibilità di definire i costruttori di mosse e spostare gli operatori di assegnazione è molto utile e crea nuove possibilità (classi puramente mobili), ma le classi che aderiscono alla C ++ 03 Rule of the Big Three andranno comunque bene.

Per le classi di gestione delle risorse, è ansible definire il gestore di copia e l’operatore di assegnazione copia come eliminati (che conta come definizione) se la risorsa sottostante non può essere copiata. Spesso vuoi ancora spostare il costruttore e spostare l’operatore di assegnazione. Gli operatori di copia e spostamento degli incarichi verranno spesso implementati utilizzando lo swap , come in C ++ 03. Se hai un costruttore di movimento e un operatore di spostamento, specializzando std::swap diventerà poco importante perché il generico std::swap usa il costruttore di movimento e l’operatore di spostamento se disponibile, e dovrebbe essere abbastanza veloce.

Le classi che non sono intese per la gestione delle risorse (cioè nessun distruttore non vuoto) o il polimorfismo del sottotipo (cioè nessun distruttore virtuale) dovrebbero dichiarare nessuna delle cinque funzioni dei membri speciali; saranno tutti generati automaticamente e si comportano in modo corretto e veloce.

Non posso credere che nessuno sia collegato a questo .

Fondamentalmente l’articolo argomenta per “Rule of Zero”. Non è appropriato per me citare l’intero articolo, ma credo che questo sia il punto principale:

Le classi che dispongono di distruttori personalizzati, costruttori copia / sposta o operatori di assegnazione copia / spostamento devono occuparsi esclusivamente della proprietà. Altre classi non dovrebbero avere distruttori personalizzati, copiare / spostare costruttori o copiare / spostare gli operatori di assegnazione.

Anche questo bit è importante per IMHO:

Le classi comuni “ownership-in-a-package” sono incluse nella libreria standard: std::unique_ptr e std::shared_ptr . Attraverso l’uso di oggetti deleter personalizzati, entrambi sono stati resi abbastanza flessibili da gestire virtualmente qualsiasi tipo di risorsa.

Io non la penso così, la regola del tre è una regola empirica che afferma che una class che implementa una delle seguenti ma non tutte è probabilmente bacata.

  1. Copia costruttore
  2. Operatore di assegnazione
  3. Distruttore

Tuttavia, lasciare fuori il costruttore di movimento o spostare l’operatore di assegnazione non implica un bug. Potrebbe essere un’occasione mancata all’ottimizzazione (nella maggior parte dei casi) o che la semantica del movimento non è rilevante per questa class, ma questo non è un bug.

Sebbene possa essere una buona pratica definire un costruttore di mosse se pertinente, non è obbligatorio. Ci sono molti casi in cui un costruttore di mosse non è rilevante per una class (es. std::complex ) e tutte le classi che si comportano correttamente in C ++ 03 continueranno a comportarsi correttamente in C ++ 0x anche se non lo fanno definire un costruttore di mosse.

Sì, penso che sarebbe bello fornire un costruttore di mosse per tali classi, ma ricorda che:

  • È solo un’ottimizzazione.

    Implementare solo uno o due del costruttore di copie, l’operatore di assegnazione o il distruttore porteranno probabilmente a bug, mentre il fatto di non avere un costruttore di movimento ridurrà potenzialmente le prestazioni.

  • Il costruttore Move non può sempre essere applicato senza modifiche.

    Alcune classi hanno sempre i loro puntatori allocati e quindi tali classi cancellano sempre i loro puntatori nel distruttore. In questi casi dovrai aggiungere controlli aggiuntivi per dire se i loro puntatori sono stati assegnati o sono stati spostati (ora sono nulli).

Ecco un breve aggiornamento sullo stato attuale e gli sviluppi correlati dal 24 gennaio 11.

Secondo lo standard C ++ 11 (vedi allegato D [depr.impldec]):

La dichiarazione implicita di un costruttore di copie è deprecata se la class ha un operatore di assegnazione di copia dichiarato dall’utente o un distruttore dichiarato dall’utente. La dichiarazione implicita di un operatore di assegnazione copie è deprecata se la class ha un costruttore di copie dichiarato dall’utente o un distruttore dichiarato dall’utente.

In realtà è stato proposto di rendere obsoleto il comportamento deprecato dando a C ++ 14 una vera “regola di cinque” anziché la tradizionale “regola dei tre”. Nel 2013 l’EWG ha votato contro questa proposta da attuare in C ++ 2014. La motivazione principale della decisione sulla proposta riguardava le preoccupazioni generali relative alla violazione del codice esistente.

Recentemente, è stato proposto di nuovo di adattare la formulazione C ++ 11 in modo da ottenere la regola informale dei Cinque, ossia quella

nessuna funzione di copia, funzione di spostamento o distruttore può essere generata dal compilatore se una di queste funzioni è fornita dall’utente.

Se approvato dall’EWG, è probabile che la “regola” venga adottata per C ++ 17.

Fondamentalmente, è come questo: se non dichiari alcuna operazione di spostamento, dovresti rispettare la regola del tre. Se dichiari un’operazione di spostamento, non c’è nulla di male nel “violare” la regola dei tre mentre la generazione di operazioni generate dal compilatore è diventata molto restrittiva. Anche se non dichiarate le operazioni di spostamento e violate la regola del tre, un compilatore C ++ 0x dovrebbe fornire un avviso nel caso in cui una funzione speciale sia stata dichiarata dall’utente e altre funzioni speciali siano state generate automaticamente a causa di un ora deprecato “regola di compatibilità C ++ 03”.

Penso che sia sicuro dire che questa regola diventa un po ‘meno significativa. Il vero problema in C ++ 03 è che l’implementazione di una semantica della copia diversa richiede all’utente di dichiarare tutte le funzioni speciali correlate in modo che nessuna di esse sia generata dal compilatore (che altrimenti farebbe la cosa sbagliata). Ma C ++ 0x cambia le regole sulla generazione della funzione membro speciale. Se l’utente dichiara solo una di queste funzioni per modificare la semantica della copia, impedirà al compilatore di generare automaticamente le restanti funzioni speciali. Ciò è positivo perché una dichiarazione mancante trasforma un errore di runtime in un errore di compilazione ora (o almeno un avvertimento). Come misura di compatibilità C ++ 03 alcune operazioni sono ancora generate ma questa generazione è considerata deprecata e dovrebbe almeno produrre un avviso in modalità C ++ 0x.

A causa delle regole piuttosto restrittive sulle funzioni speciali generate dal compilatore e sulla compatibilità C ++ 03, la regola del tre rimane la regola del tre.

Ecco alcuni esempi che dovrebbero andare bene con le più recenti regole C ++ 0x:

 template class unique_ptr { T* ptr; public: explicit unique_ptr(T* p=0) : ptr(p) {} ~unique_ptr(); unique_ptr(unique_ptr&&); unique_ptr& operator=(unique_ptr&&); }; 

Nell’esempio sopra, non è necessario dichiarare alcuna delle altre funzioni speciali come eliminate. Semplicemente non verranno generati a causa delle regole restrittive. La presenza di operazioni di spostamento dichiarate dall’utente disabilita le operazioni di copia generate dal compilatore. Ma in un caso come questo:

 template class scoped_ptr { T* ptr; public: explicit scoped_ptr(T* p=0) : ptr(p) {} ~scoped_ptr(); }; 

un compilatore C ++ 0x ora dovrebbe produrre un avvertimento su possibili operazioni di copia generate dal compilatore che potrebbero fare la cosa sbagliata. Qui, la regola di tre questioni e dovrebbe essere rispettata. Un avvertimento in questo caso è assolutamente appropriato e offre all’utente la possibilità di gestire il bug. Possiamo sbarazzarci del problema tramite funzioni cancellate:

 template class scoped_ptr { T* ptr; public: explicit scoped_ptr(T* p=0) : ptr(p) {} ~scoped_ptr(); scoped_ptr(scoped_ptr const&) = delete; scoped_ptr& operator=(scoped_ptr const&) = delete; }; 

Quindi, la regola del tre si applica ancora qui semplicemente a causa della compatibilità con C ++ 03.

Non possiamo dire che la regola 3 diventa la regola 4 (o 5) ora senza infrangere tutto il codice esistente che applica la regola 3 e non implementa alcuna forma di semantica del movimento.

Regola di 3 significa che se ne implementi devi implementare tutti e 3.

Inoltre non è consapevole che ci sarà un movimento generato automaticamente. Lo scopo della “regola del 3” è perché esistono automaticamente e se ne implementa uno, è molto probabile che l’implementazione predefinita degli altri due sia errata.

Nel caso generale, allora sì, la regola del tre è diventata il cinque, con l’operatore di assegnazione del movimento e il costruttore del movimento aggiunto. Tuttavia, non tutte le classi sono copiabili e mobili, alcune sono semplicemente mobili, altre sono semplicemente copiabili.