Question

Ce n'est pas un double de mise en œuvre du constructeur de copie en termes de opérateur = mais est une question plus précise. (Ou alors je me plais à penser.)

Intro

Étant donné une classe (hypothétique) comme ceci:

struct FooBar {
  long id;
  double valX;
  double valZ;
  long   valN;
  bool   flag; 
  NonCopyable implementation_detail; // cannot and must not be copied

  // ...
};

nous ne pouvons pas copier ce par les fonctions par défaut générées, parce que vous pouvez copier ni construction, ni copier un objet noncopyable. Cependant, cette partie de l'objet est un détail de mise en œuvre, nous sommes réellement pas intéressés à copier.

Il fait aussi ne fait aucun sens d'écrire une fonction d'échange pour cette , parce que la fonction d'échange pourrait simplement reproduire ce que std :: échange ne (moins le noncopyable).

Donc, si nous voulons copier ces objets, il nous reste la mise en œuvre de la copie cteur et copie-opérateur nous-mêmes. Cela se fait par trivialement simplement assigner les autres membres.

Question

Si nous devons mettre en œuvre une copie cteur et l'opérateur, devrions-nous mettre en œuvre le cteur de copie en termes de l'opérateur de copie, ou devrions-nous « dupliquer » le code avec la liste d'initialisation?

C'est, étant donné:

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;
}

Faut-il écrire a)

FooBar(FooBar const& rhs) {
  *this = rhs;
}

ou 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
{ }

aspects possibles une réponse par rapport à la performance serait maintenabilité par rapport à la lisibilité.

Était-ce utile?

La solution

En général, je préfère b) sur a) car il évite explicitement toute construction par défaut des membres. Pour ints, doubles, etc. qui ne sont pas une considération, mais il peut être pour les membres des opérations coûteuses ou des effets secondaires. Il est plus maintenable si vous n'avez pas à considérer ce coût / problème potentiel que vous ajoutez et supprimer des membres. Initialiser listes prennent également en charge les références et les éléments non-défaut-constructible.

Sinon, vous pourriez avoir une sous-structure des non-membres « détail de mise en œuvre » et laisser le compilateur générer du code copie, le long des lignes:

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;
    } 
};

Autres conseils

Normalement, la mise en œuvre opérateur d'affectation en termes de constructeur de copie (la version de @Roger Pate):

FooBar& operator=(FooBar copy) { swap(*this, copy); return *this; }
friend void swap(FooBar &a, FooBar &b) {/*...*/}

Cela nécessite fournir une fonction de swap qui permute membres concernés (tous sauf implementation_detail dans votre cas).

Si swap ne jette pas cette garantie d'approche de cet objet est pas laissée dans un état incohérent (avec seulement les membres de la partie attribuées).

Cependant, dans votre cas puisque ni constructeur de copie, ni opérateur d'affectation peut lancer la mise en œuvre constructeur de copie en termes d'opérateur d'affectation (a) est aussi très bien et est plus maintenable ayant alors un code presque identique dans les deux endroits (b).

Si vous êtes vraiment pris la peine sur la réplication std :: swap, pourquoi tout ne pas mettre autre que les détails de mise en œuvre dans un 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
};

vous pouvez utiliser std :: swap pour cette struct dans votre fonction de copie pour FooBar.

FooBar& operator=(const FooBar &src) {
  FooBarCore temp(src.core_)
  swap(temp,*this.core_);
  return *this;
}

D'accord, un autre essai, basé sur mon commentaire à cette réponse .

Enveloppez la implementation_detail dans une classe copiable:

class ImplementationDetail
{
public:
  ImplementationDetail() {}
  ImplementationDetail(const ImplementationDetail&) {}
  ImplementationDetail& operator=(const ImplementationDetail&) {}

public: // To make the example short
  Uncopyable implementation_detail;
};

et utiliser cette classe dans votre FooBar. La valeur par défaut généré Constructor Copier et Copier Affectation opérateur pour Foobar fonctionnera correctement.

Peut-être qu'il pourrait même tirer de incopiables afin de ne pas implementation_detail.implementation_detail tout votre code. Ou si vous contrôlez le code à la classe implementation_detail, ajoutez simplement le vide et vide Copier Constructor opérateur d'affectation.

Si le constructeur copie n'a pas besoin de copier implementation_detail et sera toujours correcte (je doute que celui-ci, mais supposons pour le moment), le implementation_detail est superflu.

Donc, la solution semble être:. Rendre la statique implementation_detail et compter sur la valeur par défaut généré Copier et Constructor opérateur d'affectation

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top