2012-02-27 5 views
56

Con le funzioni membro eliminate in modo esplicito in C++ 11, vale ancora la pena ereditare da una classe base non calcolabile?Con le funzioni membro eliminate in modo esplicito in C++ 11, vale ancora la pena ereditare da una classe base non calcolabile?

Sto parlando del trucco in cui si eredita privatamente una classe base che ha un costruttore di copie privato o cancellato e un'assegnazione di copia (ad esempio boost::noncopyable).

I vantaggi proposti in questo question sono ancora applicabili a C++ 11?


Non capisco perché alcune persone sostengono che è più semplice rendere una classe non copiabile in C++ 11.

In C++ 03:

private: 
    MyClass(const MyClass&) {} 
    MyClass& operator=(const MyClass&) {} 

In C++ 11:

MyClass(const MyClass&) = delete; 
MyClass& operator=(const MyClass&) = delete; 

EDIT:

Come molti hanno sottolineato, è stato un errore nel fornire corpi vuoti (es. {}) per il costruttore di copie private e l'operatore di assegnazione delle copie, be perché ciò consentirebbe alla classe stessa di richiamare quegli operatori senza errori. Per prima cosa ho iniziato a non aggiungere {}, ma ho riscontrato alcuni problemi di linker che mi hanno fatto aggiungere {} per qualche motivo sciocco (non ricordo le circostanze). Lo so meglio. :-)

+2

Non stai descrivendo un idioma, ma solo una sua implementazione. L'idioma rimane ed è ora più semplice scrivere. –

+0

Com'è più semplice della scrittura di un costruttore di copia privata vuoto e di un operatore di assegnazione di copia privata? –

+0

Modificato per evitare l'uso improprio del termine "idioma". –

risposta

73

Bene, questo:

private: 
    MyClass(const MyClass&) {} 
    MyClass& operator=(const MyClass&) {} 

Ancora permette tecnicamente MyClass da copiare dai soci e amici. Certo, questi tipi e funzioni sono teoricamente sotto il tuo controllo, ma la classe è ancora copiabile. Almeno con boost::noncopyable e = delete, nessuno può copiare la classe.


Non capisco perché alcune persone sostengono che è più facile fare una classe non copiabile in C++ 11.

Non è tanto più "facile" quanto "più facilmente digeribile".

Considerate questo:

class MyClass 
{ 
private: 
    MyClass(const MyClass&) {} 
    MyClass& operator=(const MyClass&) {} 
}; 

Se sei un programmatore C++ che ha letto un testo introduttivo sul C++, ma ha poco esposizione al idiomatica C++ (vale a dire: un sacco di programmatori C++), questo è ... confusionario. Dichiara i costruttori di copia e gli operatori di assegnazione delle copie, ma sono vuoti. Allora perché dichiararli a tutti? Sì, sono private, ma questo solleva solo altre domande: perché renderle private?

Per capire perché questo impedisce di copiare, devi rendertene conto dichiarandoli privati, lo fai in modo che i non membri/amici non possano copiarlo. Questo non è immediatamente ovvio per il novizio. Né è il messaggio di errore che otterranno quando provano a copiarlo.

Ora, confrontarlo con la versione C++ 11:

class MyClass 
{ 
public: 
    MyClass(const MyClass&) = delete; 
    MyClass& operator=(const MyClass&) = delete; 
}; 

Cosa ci vuole a capire che questa classe non può essere copiato? Nient'altro che capire cosa significa la sintassi = delete. Qualsiasi libro che spiega le regole di sintassi di C++ 11 ti dirà esattamente che cosa fa. L'effetto di questo codice è ovvio per l'utente inesperto di C++.

Ciò che è bello di questo idioma è che diventa un idioma perché è il modo più chiaro e più ovvio per dire esattamente cosa intendi.

Anche boost::noncopyable richiede un po 'più di riflessione. Sì, si chiama "noncopyable", quindi è auto-documentante. Ma se non l'hai mai visto prima, solleva delle domande. Perché stai derivando da qualcosa che non può essere copiato? Perché i miei messaggi di errore parlano del costruttore di copie di boost::noncopyable? Ecc. Ancora una volta, comprendere l'idioma richiede più sforzo mentale.

+12

+1 Mi piace come si illustra che '= delete' non è necessariamente più facile da scrivere, ma più semplice da * leggere *. –

+1

Voglio solo notare che gli operatori privati ​​di ctor e di assegnazione delle copie senza corpo definito farebbero il trucco. Si potrebbe semplicemente dichiarare 'MyClass (const MyClass &);' invece di fornire la definizione vuota 'MyClass (const MyClass &) {}' e in questo modo la classe non è copiabile in sé. Tuttavia '= delete' rende chiare le intenzioni del programmatore e si spiega da sé. – doc

5

È più leggibile e consente al compilatore di fornire errori migliori.

Il "cancellato" è più chiaro per un lettore, specialmente se stanno sfiorando la classe. Allo stesso modo, il compilatore può dirti che stai cercando di copiare un tipo non copiabile, invece di darti un errore generico di 'provare ad accedere ad un membro privato'.

Ma in realtà, è solo una caratteristica di convenienza.

25

La prima cosa è che, come altri prima di me sottolineano, avete ottenuto il linguaggio sbagliato, si dichiara come privato e non definire:

class noncopyable { 
    noncopyable(noncopyable const &); 
    noncopyable& operator=(noncopyable const &); 
}; 

Se il tipo di ritorno della operator= può essere praticamente qualsiasi cosa A questo punto, se leggi quel codice in un'intestazione, cosa significa in realtà? Può essere copiato solo da amici o non può essere copiato mai? Nota che se fornisci una definizione come nel tuo esempio, affermi che Posso essere copiato solo all'interno della classe e dagli amici, è la mancanza di una definizione che traduce questo in Non posso essere copiato. Ma la mancanza di una definizione nell'intestazione non è un sinonimo di mancanza di definizione ovunque, come potrebbe essere definito nel file cpp.

Questo è dove eredita da un tipo chiamato noncopyable rende esplicito che l'intenzione è quella di evitare copie, allo stesso modo che se hai scritto manualmente il codice di cui sopra si dovrebbe documento con un commento in linea che l'intenzione è disabilitare le copie.

C++ 11 non modifica nulla, rende solo la documentazione esplicita nel codice. Nella stessa riga in cui dichiari che il costruttore di copie è stato cancellato, sei documentato che lo vuoi completamente disabilitato.

Come ultimo commento, la funzione in C++ 11 non è solo di essere in grado di scrivere noncopyable con meno codice o, meglio, ma piuttosto di inibire al compilatore di generare codice che non si desidera generato. Questo è solo un uso di quella caratteristica.

+3

** Hai sollevato un punto molto importante **: le implementazioni private vuote non ti impediscono di copiare erroneamente l'oggetto! L'idioma * non * fornisce definizioni private vuote. L'idioma è di fornire * solo dichiarazioni *. –

+1

@Kuba questa soluzione non funziona con alcuni compilatori/linker conformi agli standard, poiché richiedono che quando si dichiara una funzione membro sia necessario definirla. Ad esempio, il compilatore/linker di Green Hills fallirà con un errore del linker relativo alle definizioni mancanti per il tuo operatore di copia e assegnazione, anche se nessun codice li usa. – ThreeBit

+1

È interessante. Considererei un combo compilatore/linker simile a essere rotto, poiché c'è un sacco di codice che usa questo idioma senza fornire definizioni. Mi viene in mente l'intero toolkit Qt. –

10

Oltre ai punti altri sono cresciuti ...

Avere un costruttore di copia privata e copiare operatore di assegnazione che non si definisce impedisce a chiunque di fare copie. Tuttavia, se una funzione membro o una funzione amico tenta di eseguire una copia, riceverà un errore di collegamento. Se tentano di farlo dove hai esplicitamente cancellato quelle funzioni, riceveranno un errore in fase di compilazione.

I miei errori si verificano sempre il prima possibile. Gli errori di runtime si verificano nel punto di errore anziché in un secondo momento (quindi fai in modo che l'errore si verifichi quando cambi la variabile anziché quando la leggi). Rendi tutti gli errori di runtime in errori di collegamento in modo che il codice non abbia mai la possibilità di sbagliare. Rendi tutti gli errori link-time in errori in fase di compilazione per accelerare lo sviluppo e avere messaggi di errore leggermente più utili.

2

Le persone raccomandano di dichiarare funzioni membro senza definirle. Vorrei sottolineare che un simile approccio non è portabile. Alcuni compilatori/linker richiedono che se dichiari una funzione membro, devi anche definirla, anche se non viene utilizzata. Se stai usando solo VC++, GCC, clang, puoi farla franca, ma se stai cercando di scrivere codice veramente portatile, allora alcuni altri compilatori (ad esempio Green Hills) falliranno.

+1

Sono scettico su questa risposta. Poiché è legale definire le funzioni membro di una classe su più unità di compilazione, sarebbe effettivamente difficile implementare questa anti-funzionalità. Esempio che funziona con gcc/clang/icc? – Praxeolitic