Opérateur d'affectation de surcharge en C++

comme je l'ai compris, lorsque l'opérateur de surcharge=, la valeur de retour devrait être une référence non-const.


A& A::operator=( const A& )
{
    // check for self-assignment, do assignment

    return *this;
}

Il est non-const pour permettre aux non-const fonctions de membre pour être appelé dans des cas comme:


( a = b ).f();

mais pourquoi renvoyer une référence? En l'occurrence il donner un problème si la valeur de retour n'est pas déclaré une référence, disons retour par valeur?

il est supposé que la copie constructor est implémenté correctement.

14
demandé sur jasonline 2010-03-15 17:08:18

10 réponses

ne pas renvoyer une référence est un gaspillage de ressources et une donne un design étrange. Pourquoi voulez-vous faire une copie pour tous les utilisateurs de votre opérateur même si presque tous vont rejeter cette valeur?

a = b; // huh, why does this create an unnecessary copy?

en outre, il serait surprenant pour les utilisateurs de votre classe, puisque l'opérateur de tâche intégré ne copie pas de même

int &a = (some_int = 0); // works
16
répondu Johannes Schaub - litb 2010-03-15 14:11:23

un bon conseil général lors de la surcharge des opérateurs est de 'faire comme les types primitifs font', et le comportement par défaut de l'affectation à un type primitif est cela.

ne rien renvoyer pourrait être une option, pour désactiver l'assignation à l'intérieur d'autres expressions si vous en ressentez le besoin, mais retourner une copie n'a pas de sens du tout: si l'appelant veut faire une copie, il peut le faire à partir de la référence, s'il n'a pas besoin de la copie, il n'est pas nécessaire de générer un temporaire qui n'est pas nécessaire.

14
répondu David Rodríguez - dribeas 2010-03-15 14:14:03

Cause f () peut modifier a . (nous retourner un non-const de référence)

Si nous retourner une valeur (une copie) de un , f() va modifier la copie, pas un

4
répondu mgautierfr 2010-03-15 14:15:57

Je ne sais pas combien de fois vous voulez le faire, mais quelque chose comme (a=b)=c; nécessite une référence au travail.

Edit: OK, il y a un peu plus que ça. Beaucoup de raisonnement est semi-historique. Il y a plus de raisons pour lesquelles vous ne voulez pas retourner une valeur de r que simplement éviter une copie inutile dans un objet temporaire. En utilisant une variation (mineure) sur un exemple publié à l'origine par Andrew Koenig, considérez quelque chose comme ceci:

struct Foo { 
    Foo const &assign(Foo const &other) { 
        return (*this = other);
    }
};

maintenant, supposons que vous utilisez une ancienne version de C++ où assignment retourne une valeur. Dans ce cas, (*this=other); donnera ce temporaire. Vous liez alors une référence au temporaire, détruisez le temporaire, et enfin retournez une référence pendante au temporaire détruit.

les règles qui ont été édictées depuis (prolonger la vie d'un temporaire utilisé pour initialiser une référence) atténueraient au moins (et pourrait complètement guérir) ce problème, mais I je doute que quelqu'un revoie cette situation particulière après que ces règles aient été écrites. C'était un peu comme un pilote de périphérique moche qui inclut kludges pour travailler autour de douzaines de bogues dans différentes versions et variations du matériel -- il pourrait probablement être remanié et simplifié, mais personne n'est tout à fait sûr quand un changement apparemment inoffensif va casser quelque chose qui fonctionne actuellement, et finalement personne ne veut même le regarder s'ils peuvent l'aider.

3
répondu Jerry Coffin 2010-03-15 15:27:25

Si votre opérateur d'affectation ne prenez pas un const paramètre de référence:

A& A::operator=(A&); // unusual, but std::auto_ptr does this for example.

ou si la classe A a des membres mutables (nombre de référence?), il est alors possible que l'opérateur assignment change l'objet assigné d'aussi bien que assigné à. Alors si vous aviez un code comme ceci:

a = b = c;

la cession b = c se produirait d'abord, et retourner une copie (l'appeler b' ) par la valeur au lieu de renvoi d'une référence à b . Lorsque l'assignation a = b' est effectuée, l'opérateur d'assignation Mutant changerait la copie b' au lieu de la copie réelle b .

un autre problème potentiel -- retourner par valeur plutôt que par référence pourrait causer le tranchage si vous avez des opérateurs d'affectation virtuels. Je ne dis pas que c'est une bonne idée, mais il pourrait être un problème.

Si vous avez l'intention de faire quelque chose comme (a = b).f() alors vous voudrez qu'il revienne par référence de sorte que si f() mute l'objet, il ne mute pas un temporaire.

2
répondu Dan 2010-03-15 14:48:58

en code réel (c.-à-d. pas des choses comme (a=b)=c ), retourner une valeur est peu susceptible de causer des erreurs de compilation, mais il est inefficace de retourner une copie parce que la création d'une copie peut souvent être coûteuse.

vous pouvez évidemment trouver une situation où une référence est nécessaire, mais ceux rarement, si jamais, venir dans la pratique.

1
répondu Peter Alexander 2010-03-15 14:23:24

C'est l'Article 10 de Scott Meyers excellent livre, Effective C++ . Retourner une référence de operator= n'est qu'une convention, mais c'est une bonne convention.

ce n'est qu'une convention; le code qui ne suit pas sera compilé. Toutefois, la convention est suivie par tous les types intégrés ainsi que par tous les types de la bibliothèque standard. Sauf si vous avez une bonne raison de faire les choses différemment, ne le faites pas.

1
répondu Michael Kristofik 2010-03-15 15:18:12

si vous craignez que le fait de retourner la mauvaise chose puisse causer des effets secondaires involontaires, vous pouvez écrire votre operator=() pour retourner void . J'ai vu un peu de code qui fait cela (je suppose par par paresse ou tout simplement ne pas savoir ce que le type de retour devrait être plutôt que pour la "sécurité"), et il provoque peu de problèmes. Le type d'expressions qui ont besoin d'utiliser la référence normalement retourné par operator=() sont assez rarement utilisés, et presque toujours code facile alternative pour.

Je ne suis pas sûr que je soutiendrais le retour void (dans une revue de code je l'appellerais probablement comme quelque chose que vous ne devriez pas faire), mais je le jette là-bas comme une option à considérer si vous voulez ne pas avoir à vous soucier de la façon dont les utilisations oddball de l'opérateur d'affectation pourrait être traitée.


fin edit:

aussi, j'aurais dû mentionner à l'origine que vous pouvez partager la différence en demandant à votre operator=() de retourner un const& - qui vous permettra toujours d'enchaîner les assignations:

a = b = c;

mais refusera certaines des utilisations les plus inhabituelles:

(a = b) = c;

noter que cela rend l'opérateur d'affectation ont une sémantique similaire à ce qu'elle a en C, où la valeur retournée par l'opérateur = n'est pas une valeur l. En C++, le standard l'a changé pour que l'opérateur = renvoie le type de l'opérande de gauche, donc c'est une valeur de l, mais comme Steve Jessop l'a noté dans un commentaire à une autre réponse, alors que cela en fait ainsi le compilateur acceptera

(a = b) = c;

même pour les encastrés, le résultat est un comportement non défini pour les encastrés puisque a est modifié deux fois sans point de séquence intermédiaire. Ce problème est évité pour les non-builtins avec un operator=() parce que l'appel de fonction operator=() est un point de séquence.

1
répondu Michael Burr 2010-03-15 18:02:01

S'il renvoie une copie, il vous faudra implémenter le constructeur de copie pour presque tous les objets non-triviaux.

en outre, il causerait des problèmes si vous déclarez le constructeur de copie privée mais laissez l'opérateur d'affectation publique... vous obtiendrez une erreur de compilation si vous avez essayé d'utiliser l'opérateur d'affectation en dehors de la classe ou de ses instances.

sans parler des problèmes plus graves déjà mentionnés. Vous ne voulez pas qu'il soit un copie de l'objet, vous avez vraiment ne voulez qu'il font référence au même objet. Les changements à l'un devraient être visibles pour les deux, et cela ne fonctionne pas si vous retournez une copie.

0
répondu patros 2010-03-15 14:23:57

retour par référence réduit le temps d'exécution des opérations enchaînées. Par exemple: :

a = b = c = d;

voyons les actions qui seraient appelées, si operator= retourne en valeur.

  1. Copier affectation opertor= pour c fait c égal à d , puis crée temporaire anonyme objet (appels copie ctor). Appelons-le tc .
  2. Alors operator= pour b est appelé. L'objet latéral droit est tc. L'opérateur de déménagement est appelé. b devient égal à tc . Et puis fonction Copie b à l'anonyme temporaire, appelons-le tb .
  3. La même chose se produit à nouveau, a.operator= renvoie copie temporaire de a . Après l'opérateur ; les trois objets temporaires sont détruits

au total: 3 copies ctors, 2 déplacez les opérateurs, 1 copie de l'opérateur

voyons ce qui changera si operator= retournera la valeur par référence:

  1. L'opérateur de copie est appelé. c devient égal à d , la référence à l'objet lvalue est retournée
  2. pareil. b devient égal à c , la référence à l'objet lvalue est retournée
  3. pareil. a devient égal à b , renvoi à l'objet valeur est retourné

au total: seulement trois opérateurs de copie sont appelés et aucun cteur du tout!

en outre je vous recommande de retourner la valeur par référence const, il ne vous permettra pas d'écrire le code délicat et peu évident. Avec un code plus propre, les bogues seront beaucoup plus faciles :) ( a = b ).f(); est préférable de diviser en deux lignes a=b; a.f(); .

P. : Opérateur de copie: operator=(const Class& rhs) .

opérateur d'assignation de Déplacement : operator=(Class&& rhs) .

0
répondu yanpas 2016-04-27 20:53:31