Question

J'apprends C ++ et j'ai récemment appris (ici dans Stack Overflow) sur l'idiome de copie et d'échange et j'ai quelques questions à ce sujet. Supposons donc que j'ai la classe suivante en utilisant un idiome de copie et d'échange, juste par exemple:

class Foo {
private:
  int * foo;
  int size;

public:
  Foo(size_t size) : size(size) { foo = new int[size](); }
  ~Foo(){delete foo;}

  Foo(Foo const& other){
    size = other.size;
    foo = new int[size];
    copy(other.foo, other.foo + size, foo);
  }

  void swap(Foo& other) { 
    std::swap(foo,  other.foo);  
    std::swap(size, other.size); 
  }

  Foo& operator=(Foo g) { 
    g.swap(*this); 
    return *this; 
  }

  int& operator[] (const int idx) {return foo[idx];}
};

Ma question est, supposons que j'ai une autre classe qui a un objet FOO comme données mais pas de pointeurs ou d'autres ressources qui pourraient nécessiter une copie ou une affectation personnalisée:

class Bar {
private:
  Foo bar;
public:
  Bar(Foo foo) : bar(foo) {};
  ~Bar(){};
  Bar(Bar const& other) : bar(other.bar) {}; 
  Bar& operator=(Bar other) {bar = other.bar;}
};

Maintenant, j'ai une série de questions:

  1. Les méthodes et les constructeurs sont-ils mis en œuvre ci-dessus pour le Bar Classe SAFE? Ayant utilisé la copie-et-échange pour Foo assurez-vous que aucun mal ne peut être fait lors de l'attribution ou de la copie Bar?

  2. Passer l'argument par référence dans le constructeur de copie et dans Swap est-il obligatoire?

  3. Est-il juste de dire que lorsque l'argument de operator= est passé par valeur, le constructeur de copie est appelé à cet argument pour générer une copie temporaire de l'objet, et que c'est cette copie qui est ensuite échangée avec *this? Si je suis passé par référence dans operator= J'aurais un gros problème, non?

  4. Y a-t-il des situations dans lesquelles cet idiome ne fournit pas une sécurité complète dans la copie et l'attribution Foo?

Était-ce utile?

La solution

1 - Les méthodes et les constructeurs sont-ils implémentés ci-dessus pour la classe BAR SAFE? Ayant utilisé la copie-et-échange pour FOO, vous assurez-vous qu'aucun mal ne peut être fait lors de l'attribution ou de la copie de la barre?

Concernant la copie-cotor: c'est toujours sûr (tout ou rien). Il termine (tout), soit il lance une exception (rien).

Si votre classe n'est composée que d'un seul membre (c'est-à-dire pas de classes de base également), l'opérateur d'affectation sera tout aussi sûr que celui de la classe du membre. Si vous avez plus qu'un membre, l'opérateur d'affectation ne sera plus "tout ou rien". L'opérateur d'affectation du deuxième membre pourrait lancer une exception et, dans ce cas, l'objet sera attribué "à mi-chemin". Cela signifie que vous devez implémenter à nouveau la copie et l'échange dans la nouvelle classe pour obtenir une affectation "tout ou rien".

Cependant, il sera toujours "sûr", dans le sens où vous ne divulguerez aucune ressource. Et bien sûr, l'état de chaque membre individuellement sera cohérent - seul l'état de la nouvelle classe ne sera pas cohérent, car un membre a été affecté et l'autre ne l'était pas.

2 - Passer l'argument par référence dans le constructeur de copie et dans Swap est obligatoire?

Oui, passer par référence est obligatoire. Le constructeur de copie est celui qui copie des objets, donc ne peux pas Prenez son argument par valeur, car cela signifierait que l'argument doit être copié. Cela conduirait à une récursivité infinie. (La copie-cotor serait appelée pour l'argument, ce qui signifierait appeler la copie-cotor pour l'argument, ce qui signifierait ...). Pour Swap, la raison en est une autre: si vous deviez passer l'argument par valeur, vous ne pourriez jamais utiliser Swap pour vraiment échanger le contenu de deux objets - la "cible" de l'échange serait une copie de l'objet initialement transmis, qui serait immédiatement détruit.

3 - Est-il juste de dire que lorsque l'argument de l'opérateur = est passé par valeur, le constructeur de copie est appelé à cet argument pour générer une copie temporaire de l'objet, et que c'est cette copie qui est ensuite échangée avec * ceci? Si je passais par référence dans l'opérateur = j'aurais un gros problème, non?

Oui c'est vrai. Cependant, il est également assez courant de prendre l'argument par référence à contester, de construire une copie locale, puis d'échanger avec la copie locale. La par référence à contes Way présente cependant certains inconvénients (il désactive certaines optimisations). Si vous êtes ne pas Implémentation de la copie-et-échange cependant, vous devriez probablement passer par référence à constante.

4 - Y a-t-il des situations dans lesquelles cet idiome n'offre pas une sécurité complète dans la copie et l'attribution de FOO?

Aucun que je connais. Bien sûr, on peut toujours faire échouer les choses avec le multi-threading (s'il n'est pas correctement synchronisé), mais cela devrait être évident.

Autres conseils

Dans la mesure du possible, vous devez initialiser les membres de votre classe dans la liste d'initialisateur. Cela prendra également soin du bug dont je vous ai parlé dans le commentaire. Dans cet esprit, votre code devient:

class Foo {
private:
  int size;
  int * foo;

public:
  Foo(size_t size) : size(size), foo(new int[size]) {}
  ~Foo(){delete[] foo;} // note operator delete[], not delete

  Foo(Foo const& other) : size(other.size), foo(new int[other.size]) {
    copy(other.foo, other.foo + size, foo);
  }

  Foo& swap(Foo& other) { 
    std::swap(foo,  other.foo);  
    std::swap(size, other.size); 
    return *this;
  }

  Foo& operator=(Foo g) { 
    return swap(g); 
  }

  int& operator[] (const int idx) {return foo[idx];}
};

et

class Bar {
private:
  Foo bar;
public:
  Bar(Foo foo) : bar(foo) {};
  ~Bar(){};
  Bar(Bar const& other) : bar(other.bar) { }
  Bar& swap(Bar &other) { bar.swap(other.bar); return *this; }
  Bar& operator=(Bar other) { return swap(other); }
}

qui utilise le même idiome tout au long

Remarque

Comme mentionné dans un commentaire sur la question, BarLes constructeurs de copie personnalisés, etc. sont inutiles, mais nous supposerons Bar a d'autres choses aussi :-)

deuxième question

Passer par référence à swap est nécessaire car les deux instances sont modifiées.

Passer par référence au constructeur de copie est nécessaire car en passant par valeur, vous devez appeler le constructeur de copie

troisième question

oui

quatrième question

Non, mais ce n'est pas toujours le moyen le plus efficace de faire les choses

Ceci est un exemple classique de la suite d'un idiome conduit à des pénalités de performance inutiles (pessimisation prématurée). Ce n'est pas de ta faute. L'idiome de copie et d'échappement est très exagéré. Ce est un bon idiome. Mais ça devrait ne pas être suivi aveuglément.

Noter: L'une des choses les plus chères que vous puissiez faire sur un ordinateur est d'allouer et de traiter la mémoire. Il vaut la peine d'éviter de le faire quand il est pratique.

Noter: Dans votre exemple, l'idiome de copie et d'échange effectue toujours une affaire, et souvent (lorsque le RHS de l'affectation est également une allocation LVALUE).

Observation: Lorsque size() == rhs.size(), aucun traitement ou allocation ne doit être fait. Tout ce que vous avez à faire est un copy. C'est beaucoup, Plus vite.

Foo& operator=(const Foo& g) {
    if (size != g.size)
        Foo(g).swap(*this); 
    else
       copy(other.foo, other.foo + size, foo);
    return *this; 
}

IE Vérifiez le cas où vous pouvez recycler les ressources en premier. Ensuite, copier-et-échange (ou autre) si vous ne pouvez pas recycler vos ressources.

Noter: Mes commentaires ne contredisent pas les bonnes réponses données par d'autres, ni aucun problème d'exactitude. Mon commentaire ne concerne qu'une pénalité de performance significative.

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