2010-08-26 24 views
7

Ho una classe C++ mediamente complessa che contiene un set di dati letti dal disco. Contiene un mix eclettico di galleggianti, intarsi e strutture ed è ora in uso generale. Durante una revisione del codice principale è stato chiesto se abbiamo un operatore di assegnazione personalizzato o ci affidiamo alla versione generata dal compilatore e, in caso affermativo, come facciamo a sapere che funziona correttamente? Bene, noi non ha scritto Assegnazione personalizzata e quindi una prova di unità è stato aggiunto per controllare che se facciamo:Operatore di assegnazione C++ - compilatore generato o personalizzato?

CalibDataSet datasetA = getDataSet(); 
CalibDataSet dataSetB = datasetA; 

poi datasetB è lo stesso di datasetA. Un paio di centinaia di righe o giù di lì. Ora il cliente afferma che non possiamo fare affidamento sul fatto che il compilatore (gcc) sia corretto per le versioni future e dovremmo scrivere il nostro. Hanno ragione di insistere su questo?

Ulteriori informazioni:

Sono impressionato dalle risposte/commenti già postato e il modo di risposta time.Another di porre questa domanda potrebbe essere: Quand'è che una struttura POD/classe di diventare un 'non' POD struttura/classe?

+3

Si noti che l'esempio di codice non richiama l'operatore di assegnazione copia. Invoca il costruttore di copie. '=' Qui non è un compito, è un'inizializzazione. Avresti bisogno di qualcosa sulla falsariga di "CalibDataSet dataSetB; dataSetB = datasetA; 'per richiamare l'operatore di assegnazione copia. –

+0

Beh, dipende. Mostra la definizione di CalibDataSet. Gestisce le risorse? –

+4

Regola empirica: se tutto nella classe può e deve essere copiato da 'a.thing = b.thing', allora l'operatore generato automaticamente probabilmente sta bene. –

risposta

13

È ben noto ciò che farà l'operatore di assegnazione generato automaticamente, definito come parte dello standard e un compilatore C++ conforme agli standard genererà sempre un operatore di assegnazione correttamente funzionante (se non lo fosse, quindi non sarebbe un compilatore conforme agli standard).

In genere, è necessario scrivere il proprio operatore di assegnazione solo se è stato creato il proprio distruttore o costruttore di copie. Se non ne hai bisogno, non hai nemmeno bisogno di un operatore di assegnazione.

+9

Questa è chiamata la regola del tre. Se la tua classe gestisce qualsiasi risorsa (un indicatore che fa è che tu rilasci manualmente alcune risorse nel distruttore), devi assicurarti che la semantica della copia funzioni. Usa l'idioma [copia e swap] (http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom). – GManNickG

0

Immagino che il problema della copia superficiale di una copia profonda possa apparire nel caso si abbia a che fare con stringhe. http://www.learncpp.com/cpp-tutorial/912-shallow-vs-deep-copying/

ma penso che sia sempre consigliabile scrivere sovraccarichi di assegnazione per classi definite dall'utente.

+4

Qui non sono d'accordo con te: dovresti conoscere le tue classi e scrivere gli operatori quando necessario.Scriverli inutilmente li rende codice che si deve mantenere - se il contenuto della classe non ha bisogno di un operatore di assegnazione personalizzato, quindi scriverne uno creerà più bug (a lungo termine) rispetto a non scriverne uno. –

2

Se il compilatore non sta generando correttamente il compito, allora ci sono problemi più grandi di cui preoccuparsi rispetto all'implementazione del sovraccarico dell'assegnazione (come il fatto che si abbia un compilatore danneggiato). A meno che la classe non contenga puntatori, non è necessario fornire il proprio sovraccarico; tuttavia, è ragionevole richiedere un sovraccarico esplicito, non perché il compilatore possa rompere (che è assurdo), ma piuttosto per documentare la tua intenzione che l'assegnazione sia consentita e si comporti in quel modo. In C++ 0x, sarà possibile documentare l'intento e risparmiare tempo utilizzando = default per la versione generata dal compilatore.

+0

Interessante. Attendo con ansia il C++ 0x – ExpatEgghead

+0

"è ragionevole richiedere un sovraccarico esplicito" - ummm ... no. Un commento sarebbe sufficiente, è meno incline agli errori e potrebbe essere più efficiente. L'attuale pratica del C++ funziona esattamente nell'altro senso: per garantire che non sia destinato a un uso pubblico, è necessario utilizzare un operatore di assegnazione privato. C++ 0x potrebbe migliorare su questo, ma non è rilevante oggi. –

+0

@Tony, in genere sono d'accordo, ma non è insolito definirlo esplicitamente a scopo di documentazione. Inoltre, quando si usa Doxygen o un altro generatore di documentazione automatico, un commento di solito non lo taglia. Inoltre, se lo si definisce esplicitamente, si dovrebbe anche testarlo unitamente, il quale rileverà delle rotture se, ad esempio, è stato aggiunto un campo puntatore. –

1

Se nella tua classe ci sono alcune risorse che dovresti gestire, allora scrivere il tuo operatore di assegnazione ha senso affidarsi a quello generato dal compilatore.

+0

Nessuna risorsa di alcun tipo. Neanche a stringhe – ExpatEgghead

8

Il motivo più comune per definire esplicitamente un operatore di assegnazione è supportare "proprietà remota", in pratica una classe che include uno o più puntatori e che detiene le risorse a cui tali puntatori fanno riferimento. In tal caso, normalmente è necessario definire l'assegnazione, la copia (cioè il costruttore di copie) e la distruzione. Ci sono tre strategie principali per questi casi (ordinati per frequenza decrescente di utilizzo):

  1. copia completa
  2. riferimento contando
  3. trasferimento di proprietà

copia profonda significa allocare una nuova risorsa per l'obiettivo del compito/copia. Per esempio., una classe stringa ha un puntatore al contenuto della stringa; quando lo assegni, l'assegnazione alloca un nuovo buffer per contenere il nuovo contenuto nella destinazione e copia i dati dall'origine nel buffer di destinazione. Viene utilizzato nella maggior parte delle implementazioni correnti di un numero di classi standard come std::string e std::vector.

Il conteggio di riferimento utilizzato anche è abbastanza comune. Molte (più?) Implementazioni precedenti di std::string utilizzavano il conteggio dei riferimenti. In questo caso, invece di allocare e copiare i dati per la stringa, è semplicemente stato incrementato un conteggio di riferimento per indicare il numero di oggetti stringa che fanno riferimento a un particolare buffer di dati. Hai assegnato un nuovo buffer solo quando/se il contenuto di una stringa è stato modificato in modo tale che doveva differire dagli altri (ad esempio, utilizzava la copia su scrittura). Con il multithreading, tuttavia, è necessario sincronizzare l'accesso al conteggio dei riferimenti, che spesso ha un grave impatto sulle prestazioni, quindi nel codice più recente questo è abbastanza inusuale (usato principalmente quando qualcosa memorizza quindi molti dati che vale la pena potenzialmente perdere un po ' del tempo di CPU per evitare tale copia).

Il trasferimento di proprietà è relativamente inusuale. È ciò che è fatto da std::auto_ptr. Quando assegni o copi qualcosa, la fonte del compito/copia viene sostanzialmente distrutta - i dati vengono trasferiti da uno all'altro. Questo è (o può essere) utile, ma la semantica è sufficientemente diversa dall'assegnazione normale che è spesso controintuitiva. Allo stesso tempo, nelle giuste circostanze, può fornire grande efficienza e semplicità. C++ 0x renderà il trasferimento della proprietà considerevolmente più gestibile aggiungendo un tipo unique_ptr che lo rende più esplicito e aggiungendo anche riferimenti rvalue, che rendono facile implementare il trasferimento della proprietà per una classe abbastanza ampia di situazioni in cui può migliorare le prestazioni senza che conduce alla semantica visibilmente controintuitiva.

Tornando alla domanda originale, tuttavia, se non si dispone della proprietà remota per iniziare - ad esempio, la classe non contiene alcun puntatore, è probabile che non si debba definire esplicitamente un operatore di assegnazione (o dtor o copy ctor). Un bug del compilatore che impediva agli operatori di assegnazione definiti implicitamente di funzionare avrebbe impedito il superamento di un numero enorme di test di regressione.

Anche se in qualche modo venisse rilasciato, la vostra difesa contro di esso sarebbe semplicemente non usarlo. Non c'è un vero spazio per la domanda che una tale versione possa essere sostituita nel giro di poche ore. Con un progetto esistente di dimensioni medio-grandi, non si desidera passare a un compilatore finché non viene utilizzato per un certo periodo di tempo.

Problemi correlati