Depuis un constructeur de copie
MyClass(const MyClass&);
et an = surcharge de l'opérateur
MyClass& operator = (const MyClass&);
ont à peu près le même code, le même paramètre, et ne diffèrent que sur le retour, est-il possible d'avoir une fonction commune pour les deux à utiliser?
c++
variable-assignment
copy-constructor
c++-faq
MPelletier
la source
la source
Réponses:
Oui. Il existe deux options courantes. L'une - qui est généralement déconseillée - consiste à appeler
operator=
explicitement le constructeur de copie:MyClass(const MyClass& other) { operator=(other); }
Cependant, fournir un bien
operator=
est un défi lorsqu'il s'agit de gérer l'ancien état et les problèmes liés à l'auto-affectation. En outre, tous les membres et bases sont initialisés par défaut en premier, même s'ils doivent être affectés à fromother
. Cela peut même ne pas être valable pour tous les membres et bases et même là où c'est valable, cela est sémantiquement redondant et peut être pratiquement coûteux.Une solution de plus en plus populaire consiste à implémenter en
operator=
utilisant le constructeur de copie et une méthode d'échange.MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }
ou même:
MyClass& operator=(MyClass other) { swap(other); return *this; }
Une
swap
fonction est généralement simple à écrire car elle échange simplement la propriété des composants internes et n'a pas à nettoyer l'état existant ou à allouer de nouvelles ressources.Les avantages de l'idiome de copie et d'échange est qu'il est automatiquement sûr pour l'auto-affectation et - à condition que l'opération d'échange ne soit pas lancée - est également fortement protégé contre les exceptions.
Pour être parfaitement sûr d'exception, un opérateur d'affectation écrit `` à la main '' doit généralement allouer une copie des nouvelles ressources avant de désallouer les anciennes ressources du cessionnaire de sorte que si une exception se produit en allouant les nouvelles ressources, l'ancien état peut toujours être retourné à . Tout cela est gratuit avec copie et échange, mais est généralement plus complexe, et donc sujet aux erreurs, à faire à partir de zéro.
La seule chose à laquelle il faut faire attention est de s'assurer que la méthode swap est un vrai swap, et non la méthode par défaut
std::swap
qui utilise le constructeur de copie et l'opérateur d'affectation lui-même.En règle générale, un membre
swap
est utilisé.std::swap
fonctionne et est garanti «no-throw» avec tous les types de base et les types de pointeurs. La plupart des pointeurs intelligents peuvent également être échangés avec une garantie sans jet.la source
operator=
partir du cteur de copie est en fait assez mauvais, car il initialise d'abord toutes les valeurs avec une valeur par défaut juste pour les remplacer par les valeurs de l'autre objet juste après.assign
fonction membre utilisée à la fois par le cteur de copie et l'opérateur d'affectation dans certains cas (pour les classes légères). Dans d'autres cas (cas d'utilisation intensive de ressources, descripteur / corps), une copie / échange est la voie à suivre bien sûr.Le constructeur de copie effectue la première initialisation des objets qui étaient auparavant de la mémoire brute. L'opérateur d'affectation, OTOH, remplace les valeurs existantes par de nouvelles. Le plus souvent, cela implique de rejeter les anciennes ressources (par exemple, la mémoire) et d'en allouer de nouvelles.
S'il y a une similitude entre les deux, c'est que l'opérateur d'affectation effectue la destruction et la copie-construction. Certains développeurs avaient l'habitude de mettre en œuvre l'attribution par destruction sur place suivie d'une construction de copie de placement. Cependant, c'est une très mauvaise idée. (Que faire s'il s'agit de l'opérateur d'affectation d'une classe de base qui a appelé lors de l'affectation d'une classe dérivée?)
Ce qui est généralement considéré comme l'idiome canonique de nos jours utilise
swap
comme Charles l'a suggéré:MyClass& operator=(MyClass other) { swap(other); return *this; }
Cela utilise la construction par copie (notez que
other
c'est copié) et la destruction (c'est détruit à la fin de la fonction) - et il les utilise aussi dans le bon ordre: construction (peut échouer) avant la destruction (ne doit pas échouer).la source
swap
être déclarévirtual
?Quelque chose me dérange:
MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }
Premièrement, lire le mot «swap» quand mon esprit pense «copier» irrite mon bon sens. Aussi, je remets en question le but de cette astuce sophistiquée. Oui, toutes les exceptions dans la construction des nouvelles ressources (copiées) devraient se produire avant l'échange, ce qui semble être un moyen sûr de s'assurer que toutes les nouvelles données sont remplies avant de les mettre en ligne.
C'est très bien. Alors, qu'en est-il des exceptions qui se produisent après l'échange? (lorsque les anciennes ressources sont détruites lorsque l'objet temporaire est hors de portée) Du point de vue de l'utilisateur de l'affectation, l'opération a échoué, sauf que ce n'est pas le cas. Cela a un énorme effet secondaire: la copie a effectivement eu lieu. Seul un nettoyage des ressources a échoué. L'état de l'objet de destination a été modifié même si l'opération semble avoir échoué de l'extérieur.
Donc, je propose au lieu de "swap" de faire un "transfert" plus naturel:
MyClass& operator=(const MyClass& other) { MyClass tmp(other); transfer(tmp); return *this; }
Il y a toujours la construction de l'objet temporaire, mais la prochaine action immédiate est de libérer toutes les ressources actuelles de la destination avant de déplacer (et de NULL pour qu'elles ne soient pas libérées deux fois) les ressources de la source vers elle.
Au lieu de {construire, déplacer, détruire}, je propose {construire, détruire, déplacer}. Le mouvement, qui est l'action la plus dangereuse, est celui qui est effectué en dernier après que tout le reste a été réglé.
Oui, l'échec de la destruction est un problème dans les deux schémas. Les données sont soit corrompues (copiées lorsque vous ne pensiez pas qu'elles l'étaient) ou perdues (libérées lorsque vous ne pensiez pas qu'elles l'étaient). Perdu vaut mieux que corrompu. Aucune donnée n'est meilleure que de mauvaises données.
Transférer au lieu de swap. C'est ma suggestion de toute façon.
la source
First, reading the word "swap" when my mind is thinking "copy" irritates
-> En tant qu'écrivain de bibliothèque, vous connaissez généralement les pratiques courantes (copie + échange), et le point crucial estmy mind
. Votre esprit est en fait caché derrière l'interface publique. C'est ce qu'est le code réutilisable.