Implementare copia-ctor in termini di copia-operatore o separatamente?
-
29-09-2019 - |
Domanda
Questo non è un duplicato di Attuazione del costruttore di copia in termini di operatore = ma è una domanda più specifica. (O almeno così mi piace pensare.)
Introduzione
Dato un (ipotetico) classe come questa:
struct FooBar {
long id;
double valX;
double valZ;
long valN;
bool flag;
NonCopyable implementation_detail; // cannot and must not be copied
// ...
};
non possiamo copiare questo dalle funzioni predefinite generate, perché si può né copiare né costrutto copiare un oggetto NonCopyable. Tuttavia, questa parte l'oggetto è un dettaglio di implementazione che non sono realmente interessati a copiare.
Lo fa anche non ha alcun senso per scrivere una funzione di scambio per questo , perché la funzione di scambio potrebbe semplicemente replicare quello che std :: di swap fa (meno il NonCopyable).
Quindi, se vogliamo copiare questi oggetti, ci ritroviamo con l'attuazione del copia-ctor e copia-operatore di noi stessi. Questo è banalmente fatto solo assegnando gli altri membri.
Domanda
Se abbiamo bisogno di implementare la copia ctor e l'operatore, dovremmo implementare il ctor copia in termini di operatore di copia, o dovremmo "duplicato" il codice con la lista di inizializzazione?
Cioè, dato:
FooBar& operator=(FooBar const& rhs) {
// no self assignment check necessary
id = rhs.id;
valX = rhs.valX;
valZ = rhs.valZ;
valN = rhs.valN;
flag = rhs.flag;
// don't copy implementation_detail
return *this;
}
dovremmo scrivere)
FooBar(FooBar const& rhs) {
*this = rhs;
}
oppure b)
FooBar(FooBar const& rhs)
: id(rhs.id)
, valX(rhs.valX)
, valZ(rhs.valZ)
, valN(rhs.valN)
, flag(rhs.flag)
// don't copy implementation_detail
{ }
I possibili aspetti di una risposta sarebbe prestazioni vs. manutenibilità contro la leggibilità.
Soluzione
In generale, preferisco b) sopra a) in quanto evita esplicitamente qualsiasi costruzione di default dei membri. Per interi, ecc doppie che non è una considerazione, ma può essere per membri con operazioni costose o effetti collaterali. E 'più gestibile se non c'è bisogno di prendere in considerazione questo potenziale costo / problema, come si sta aggiungendo e rimuovendo membri. Initialiser liste supportano anche i riferimenti e gli elementi non-default-costruibile.
In alternativa, si potrebbe avere un sub-struttura per le non "implementazione di dettaglio" i membri e lasciare che il compilatore genera copiando il codice, lungo le linee:
struct X
{
struct I
{
int x_;
int y_;
} i_;
int z_;
X() { }
X(const X& rhs)
: i_(rhs.i_), z_(0) // implementation not copied
{ }
X& operator=(const X& rhs)
{
i_ = rhs.i_;
return *this;
}
};
Altri suggerimenti
Normalmente si implementa operatore di assegnazione in termini di costruttore di copia (la versione di @Roger Pate):
FooBar& operator=(FooBar copy) { swap(*this, copy); return *this; }
friend void swap(FooBar &a, FooBar &b) {/*...*/}
Questo richiede una funzione di swap
che scambia membri interessati (tutti tranne implementation_detail
nel tuo caso).
Se swap
non gettare questo approccio garantisce che oggetto non è lasciato in uno stato incoerente (con solo i membri parte assegnata).
Tuttavia, nel tuo caso, dato che né costruttore di copia, né operatore di assegnazione può gettare attuazione costruttore di copia in termini di operatore di assegnazione (a) è anche bene ed è più gestibile quindi avere il codice quasi identico in entrambi i posti (b).
Se sei veramente preoccupato di replicare std :: scambio, perché non mettere tutto diverso dal dettaglio di implementazione in una struct?
struct FooBarCore {
long id;
double valX;
double valZ;
long valN;
bool flag;
// ...
};
struct FooBar {
FooBarCore core_;
NonCopyable implementation_detail; // cannot and must not be copied
};
quindi è possibile utilizzare std :: swap per questa struct nella vostra funzione di copia per FooBar
.
FooBar& operator=(const FooBar &src) {
FooBarCore temp(src.core_)
swap(temp,*this.core_);
return *this;
}
D'accordo, un altro tentativo, in base alla mia commento a questa risposta .
Avvolgere l'implementation_detail in una classe copiabile:
class ImplementationDetail
{
public:
ImplementationDetail() {}
ImplementationDetail(const ImplementationDetail&) {}
ImplementationDetail& operator=(const ImplementationDetail&) {}
public: // To make the example short
Uncopyable implementation_detail;
};
e utilizzare questa classe nel FooBar. Il valore di default generato costruttore di copia e Copia assegnazione operatore per Foobar funziona correttamente.
Forse potrebbe anche derivare da Uncopyable modo non si ottiene implementation_detail.implementation_detail
su tutto il codice. Oppure, se si controlla il codice alla classe implementation_detail, basta aggiungere il vuoto costruttore di copia e assegnazione vuota Operator.
Se il costruttore di copia non ha bisogno di copiare implementation_detail ed ancora non sono precisi (dubito quest'ultimo, ma supponiamo che per il momento), l'implementation_detail è superfluo.
Quindi, la soluzione sembra essere:. Fare la statica implementation_detail e si basano sul predefinito generato costruttore di copia e Operatore di assegnazione