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à.

È stato utile?

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top