2010-01-31 13 views

risposta

80
class Foo { 
    private: 
    Foo(); 
    Foo(const Foo& other); // non construction-copyable 
    Foo& operator=(const Foo&); // non copyable 
    public: 
    static Foo* create(); 
} 

Se stai usando spinta, è possibile anche ereditare da noncopyable: http://www.boost.org/doc/libs/1_41_0/boost/noncopyable.hpp

EDIT: C++ 11 versione se si dispone di un compilatore che supportano questa funzione:

class Foo { 
    private: 
    Foo(); 
    Foo(const Foo& other) = delete; // non construction-copyable 
    Foo& operator=(const Foo&) = delete; // non copyable 
    public: 
    static Foo* create(); 
} 
+1

Per motivi di interesse, perché hai reso privato il costruttore predefinito e aggiunto un metodo create()? Quali vantaggi ha questo layout? – user3728501

+0

@EdwardBird Stavo solo usando l'esempio di domanda. Questo modo di fare è fondamentalmente come forzare la costruzione di istanze di un particolare tipo attraverso una fabbrica. Ciò è utile se il costruttore deve eseguire l'impostazione di base e alcune altre operazioni (magari diverse a seconda del contesto o della piattaforma o qualsiasi altra cosa) prima di fornire l'oggetto, o anche prima di creare l'oggetto (forse qualche manipolazione del pool di memoria). Avrei usato un unique_ptr o shared_ptr come create() restituisce il tipo personalmente. In ogni caso, il motivo principale era solo la correzione dell'esempio della domanda. – Klaim

+0

Ah, grazie, è una buona cosa essere a conoscenza. – user3728501

3

Rendi privato il costruttore della copia.

Foo(const Foo& src); 

Non è necessario implementarlo, basta dichiararlo nel file di intestazione.

25

Rende privato anche il costruttore di copie e l'operatore di assegnazione. Solo la dichiarazione è sufficiente, non è necessario fornire un'implementazione.

+12

E non si dovrebbe fornire un'implementazione. –

4

Il modo tipico per rendere un oggetto C++ non copiabile è dichiarare esplicitamente un costruttore di copia e un operatore di assegnazione delle copie ma non implementarli. Ciò impedirà al compilatore di generare il proprio. (In genere questo viene fatto in collaborazione con dichiarandoli private in modo da generare un errore di compilazione, invece di un errore di linker.)

C'è anche la classe boost::noncopyable che si può ereditare da, che fa quello che ho descritto sopra.

16
#include <boost/utility.hpp> 
class Foo : boost::noncopyable {... 

Ma come disse una volta Scott Meyers ... "È una bella classe, è solo che trovo il nome un po 'un, err non naturale", o qualcosa del genere.

+0

Qualche link al contesto della citazione? – GManNickG

+0

era in C++ efficace o C++ più efficace, un libro. –

+3

Riferimento: efficace C++ (terza edizione) - Scott Meyers, elemento 6 – Thirler

16

Per aggiungere un po 'lì.

La soluzione tradizionale è, come si è detto, a dichiarare sia Copy Constructor e Assignment Operator come private, e non a definire loro.

  • Perché sono private, questo porterà ad un errore di di compilazione da chiunque cerchi di usarli che non ha accesso alle parti private della classe ...
  • Quale lascia amici (e la classe stessa) per cui si verificherà l'errore sotto forma di undefined symbol, a tempo di collegamento (se si verifica per quelli lì) o molto probabilmente a tempo di esecuzione (quando si tenta di caricare la libreria).

Naturalmente, nel secondo caso è piuttosto un problema perché è necessario verificare il proprio codice in quanto non si ha l'indicazione del file e della riga in cui si verifica l'errore. Fortunatamente è limitato ai metodi e agli amici della tua classe.


Inoltre, vale la pena notare che queste proprietà sono transitivi lungo la strada ereditarietà e composizione: il compilatore solo generare versioni predefinite del Default Constructor, il Copy Constructor, il Assignment Operator e Destructor se può.

Ciò significa che per ciascuno di questi quattro, vengono generati automaticamente solo se sono accessibili per tutte le basi e gli attributi della classe.

// What does boost::noncopyable looks like > 
class Uncopyable { 
public: 
    Uncopyable() {} 

private: 
    Uncopyable(const Uncopyable&); 
    Uncopyable& operator=(const Uncopyable&); 
}; 

Questo è il motivo per ereditare da questa classe (o di usarlo come attributo) sarà previene efficacemente la propria classe per essere copiabile o cedibile a meno che non si definiscono tali operatori da soli.

Generalmente ereditarietà è scelto sopra composizione per 2 motivi:

  • l'oggetto è efficacemente Uncopyable, anche se il polimorfismo non può essere che utile
  • Inheritance conduce EBO o Empty Base Optimization, mentre un attributo sarà indirizzabile e quindi occuperà memoria (in ogni istanza della classe) anche se in realtà non ne ha bisogno, il compilatore ha la possibilità di non aggiungere questo overhead per una classe base.

Si potrebbe, in alternativa, dichiarare gli operatori privati ​​e non li definisce nella propria classe, ma il codice sarebbe meno auto-documentazione, e non sarebbe in grado di ricercare automaticamente per quelli di classe che hanno questa proprietà quindi (a meno che tu non abbia un parser completo).

Spero che questo accenda il meccanismo.

+1

+1. Buona risposta. – zeboidlund

+0

@chappjc: E tu pensi bene! Spiacenti ...: D –

+0

BTW, non è 'Uncopyable' incompleto senza definire esplicitamente il costruttore in quanto non verrà generato automaticamente a causa della presenza degli altri costruttori? Ad esempio, ottieni "nessun costruttore predefinito appropriato disponibile" con questo: http://rextester.com/SFWR22041 Grazie per la tua risposta utile! Apprezzo in particolare la motivazione che hai dato per l'utilizzo dell'ereditarietà. – chappjc

2

Questo è quello che uso:

/* Utility classes */ 

struct NoCopy 
{ 
public: 
    NoCopy() {} 
private: 
    NoCopy(const NoCopy &); 
}; 

struct NoAssign 
{ 
private: 
    NoAssign &operator=(const NoAssign &); 
}; 

struct NonInstantiable 
{ 
private: 
    NonInstantiable(); 
}; 

struct NoCopyAssign : NoCopy, NoAssign 
{ 
}; 
typedef NoCopyAssign NoAssignCopy; 

Nel tuo caso:

struct Example : NoCopy 
{ 
}; 
+2

Nota che ereditare da classi di utilità come questa può influire negativamente sulle dimensioni della classe, a seconda dell'architettura ABI. Vedi http://trac.webkit.org/changeset/68414 per i dettagli. Certo, quel changeset menziona solo Itanic e nient'altro - ma vale la pena affidarsi a nessun'altra architettura per farlo? Può essere. È un rischio definito e la dichiarazione di un costruttore privato e di un operatore incaricato funziona altrettanto bene. –

17

Solo un altro modo per non consentire costruttore di copia, per comodità, una macro DISALLOW_COPY_AND_ASSIGN può essere utilizzato:

// A macro to disallow the copy constructor and operator= functions 
// This should be used in the private: declarations for a class 
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ 
    TypeName(const TypeName&) = delete;  \ 
    void operator=(const TypeName&) = delete 

Quindi, in classe Foo:

class Foo { 
public: 
    Foo(int f); 
    ~Foo(); 

private: 
    DISALLOW_COPY_AND_ASSIGN(Foo); 
}; 

ref from google style sheet

+1

La tua soluzione non funziona così com'è con alcuni compilatori. Alcuni compilatori C++ richiedono che se dichiari una funzione membro della classe, devi anche definirla, anche se non è mai stata usata nel codice. Quindi è necessario utilizzare {} con le due dichiarazioni di funzione sopra. – ThreeBit

+0

@ThreeBit, se intendi un costruttore con un parametro e un distruttore dicendo "due funzioni", questi sono la declinazione e il programmatore sa già che questi avranno la definizione da qualche altra parte. Oltre a questo, è la stessa risposta accettata. –

+0

@ThreeBit: quali compilatori intendi? Se lo fanno, non sono conformi allo standard. –

10

In C++ 11, è possibile disattivare in modo esplicito la creazione di copia predefinita e costruttore di assegnazione ponendo = delete dopo la dichiarazione.

Da Wikipedia:

struct NonCopyable { 
    NonCopyable() = default; 
    NonCopyable(const NonCopyable&) = delete; 
    NonCopyable & operator=(const NonCopyable&) = delete; 
}; 

Lo stesso vale per le classi di corso.

Problemi correlati