82 votes

Devrais-je renvoyer des objets constants ?

Sur Effective C++ Point 03, utiliser le const chaque fois que possible.

class Bigint
{
  int _data[MAXLEN];
  //...
public:
  int& operator[](const int index) { return _data[index]; }
  const int operator[](const int index) const { return _data[index]; }
  //...
};

const int operator[] fait la différence avec int& operator[] .

Mais qu'en est-il :

int foo() { }

y

const int foo() { }

On dirait que c'est la même chose.

Ma question est, pourquoi nous utilisons const int operator[](const int index) const au lieu de int operator[](const int index) const ?

5 votes

Bonne question - mais dans ce cas, la méthode ne renverrait-elle pas une référence dans les deux cas ?

0 votes

Bonne question. Je vous suggère de le changer en int operator[](int i) contre const int operator[](const int i) .

1 votes

@KenWayneVanderLinde, non. La méthode appelée dépend de si nous avons un Bigint& o a const Bigint& .

90voto

James Kanze Points 96599

Les cv-qualificateurs de haut niveau sur les types de retour qui ne sont pas de type classe sont ignorés. Ce qui signifie que même si vous écrivez :

int const foo();

Le type de retour est int . Si le type de retour est une référence, bien sûr, le site const n'est plus de premier niveau, et la distinction entre :

int& operator[]( int index );

y

int const& operator[]( int index ) const;

est significatif. (Notez aussi que dans les déclarations de fonctions, comme celle ci-dessus, tous les qualificatifs cv de niveau supérieur sont également ignorés).

La distinction est également pertinente pour les valeurs de retour de type classe : si vous retournez T const alors l'appelant ne peut pas appeler de fonctions non constantes sur la valeur retournée, par exemple

class Test
{
public:
    void f();
    void g() const;
};

Test ff();
Test const gg();

ff().f();             //  legal
ff().g();             //  legal
gg().f();             //  **illegal**
gg().g();             //  legal

6 votes

Ff et gg sont déclarés comme des fonctions retournant Test. Si c'est le cas, les invokations devraient être ff().f() etc... Si ff est une instance de la classe Test, les parenthèses devraient être supprimées de la déclaration (+1 cependant :)

0 votes

@user396672 (et BJ...) Oui. J'ai oublié le () après ff y gg . Je vais les ajouter. Merci pour la correction.

6 votes

1 "Les cv-qualificateurs de haut niveau sur les types de retour de type non classe sont ignorés" est incorrect. Ils font toujours partie du type de la fonction. Cependant, la qualification cv d'une valeur de retour de type non-classe est ignorée, de sorte qu'un appel d'une fonction retournant int const est équivalent à un appel de cette fonction modifiée pour renvoyer int . Standardisé dans C++11 §3.10/4, "Les valeurs de classe peuvent avoir des types qualifiés cv ; les valeurs de non-classe ont toujours des types non qualifiés cv".

43voto

Bartek Banachewicz Points 13173

Vous devez clairement distinguer l'usage de const qui s'applique aux valeurs de retour, aux paramètres et à la fonction elle-même.

Valeurs de retour

  • Si la fonction renvoie par valeur le const n'a pas d'importance, puisque la copie de l'objet est retournée. Il sera cependant important en C++11 avec la sémantique de déplacement impliquée.
  • De même, cela n'a jamais d'importance pour les types de base, car ils sont toujours copiés.

Pensez à const std::string SomeMethod() const . Il ne permettra pas à la (std::string&&) à utiliser, car elle s'attend à une valeur r non constante. En d'autres termes, la chaîne retournée sera toujours copiée.

  • Si la fonction renvoie par référence , const protège l'objet retourné contre toute modification.

Paramètres

  • Si vous passez un paramètre par valeur le const empêche la modification de la valeur donnée par la fonction en fonction . Les données originales du paramètre ne peuvent de toute façon pas être modifiées, puisque vous n'avez qu'une copie.
  • Notez que puisque la copie est toujours créée, la constante n'a de signification que pour organe de fonction Ainsi, il est vérifié uniquement dans la définition de la fonction, et non dans la déclaration (interface).
  • Si vous passez un paramètre par référence la même règle que pour les valeurs de retour s'applique

La fonction elle-même

  • Si la fonction a const à la fin, il peut seulement exécuter d'autres const et ne peut pas modifier ou permettre la modification des données de la classe. Ainsi, si elle retourne par référence, la référence retournée doit être const. Seules les fonctions const peuvent être appelées sur un objet ou une référence à un objet qui est lui-même const. De même, les champs mutables peuvent être modifiés.
  • Le comportement créé par le compilateur change this référence à T const* . La fonction peut toujours const_cast this mais bien sûr, cela ne devrait pas être fait et est considéré comme dangereux.
  • Il est bien sûr judicieux de n'utiliser ce spécificateur que pour les méthodes de classe ; les fonctions globales avec const à la fin provoqueront une erreur de compilation.

Conclusion

Si votre méthode ne modifie pas et ne modifiera jamais les variables de la classe, marquez-la comme const et assurez-vous de respecter tous les critères requis. Cela permettra d'écrire un code plus propre, ce qui permettra de garder le const-correct . Cependant, en mettant const partout sans y réfléchir n'est certainement pas la voie à suivre.

4 votes

Deux corrections : d'abord, pour le retour par valeur, le top-level const est ignoré pour les types de non-classe . C'est important pour les types de classe. Et pour les paramètres passés par valeur, le niveau supérieur const est ignoré dans les déclarations de fonctions. Elle n'a de sens que dans la définition.

0 votes

Je vais intégrer ce commentaire dans ma réponse, si vous le voulez bien. Je pense que ce sera beaucoup plus clair.

0 votes

Quant à const sur une fonction : elle modifie le type de this qui est T const* plutôt que T* . Tous les autres effets en découlent. (Et cela n'empêche pas toute modification de l'objet : mutable les données peuvent toujours être modifiées, et la fonction peut légalement rejeter la const, et modifier ce qu'elle souhaite).

31voto

sbi Points 100828

Il y a petite valeur en ajoutant const aux valeurs r non référencées/non pointées, et aucun point en l'ajoutant aux encastrements.

Dans le cas de types définis par l'utilisateur , a const La qualification empêchera les appelants de invoquer un non const fonction membre sur l'objet retourné. Par exemple, étant donné

const std::string foo();
      std::string bar();

puis

foo().resize(42);

serait interdit, tandis que

bar().resize(4711);

serait autorisé.

Pour les encastrements comme int cela n'a aucun sens, car ces valeurs r ne peuvent pas être modifiées de toute façon.

(Je me souviens C++ efficace en discutant de la possibilité de rendre le type de retour de operator=() a const de référence, cependant, et c'est un élément à prendre en compte).


Editar:

Il semble que Scott a en effet donné ce conseil . Si c'est le cas, c'est pour les raisons évoquées ci-dessus, Je trouve cela discutable, même pour C++98 et C++03. . Pour C++11, je considère que c'est tout simplement faux comme Scott lui-même semble l'avoir découvert. Dans le errata pour Effective C++, 3e édition. Il écrit (ou cite d'autres personnes qui se sont plaintes) :

Le texte implique que tous les retours par valeur devraient être const, mais les cas où les retours par valeur non const sont de bonne conception ne sont pas difficiles à trouver, par exemple, les types de retour de std::vector où les appelants utiliseront swap avec un vecteur vide pour "récupérer" le contenu de la valeur de retour sans le copier.

Et plus tard :

Déclarer const les valeurs de retour des fonctions by-value empêchera qu'elles soient liées à des références rvalue dans C++0x. Les références rvalue étant conçues pour améliorer l'efficacité du code C++, il est important de tenir compte de l'interaction entre les valeurs de retour const et l'initialisation des références rvalue lors de la spécification des signatures de fonctions.

1 votes

J'aime la mention des errata avec les conseils de Scott. Cela permet vraiment d'éclairer ce qui pourrait être déroutant pour les personnes qui découvrent Effective C++.

2 votes

@sehe : J'ai envoyé un mail à Scott pour lui poser la question, et sa réponse a été que ces commentaires d'errata reflètent sa pensée actuelle sur la question.

18voto

Andrey Points 5028

Vous pourriez passer à côté de l'essentiel du conseil de Meyers. La différence essentielle est dans const pour la méthode.

Celle-ci est une méthode non-const (remarquez qu'il n'y a pas de const à la fin) ce qui signifie qu'il est autorisé à modifier l'état de la classe.

int& operator[](const int index)

Celle-ci est une méthode const (remarquez const à la fin)

const int operator[](const int index) const

En ce qui concerne les types de paramètres et de valeurs de retour, il existe une différence mineure entre les deux. int y const int mais cela n'a rien à voir avec l'objet de ce conseil. Ce à quoi vous devriez faire attention, c'est que la surcharge non-const renvoie int& ce qui signifie que vous pouvez lui assigner, par exemple num[i]=0 et la surcharge const renvoie une valeur non modifiable (peu importe si le type de retour est int o const int ).

Selon mon opinion personnelle, si un objet est transmis par valeur , const est superflu. Cette syntaxe est plus courte et permet d'obtenir le même résultat.

int& operator[](int index);
int operator[](int index) const;

5 votes

En fait, la question porte sur le retour d'un const valeur.

0 votes

@juanchopanza : Je doute que le PO voulait dire exactement ça. Comparez son foo exemple avec operator[] exemple. Il pourrait ne pas comprendre que const du modificateur de la méthode fait la différence, pas le type de retour.

16voto

Kerrek SB Points 194696

La raison principale pour laquelle les valeurs sont retournées en tant que constantes est que vous ne pouvez pas dire quelque chose comme foo() = 5; . Ce n'est pas réellement un problème avec les types primitifs, puisque vous ne pouvez pas assigner aux valeurs r des types primitifs, mais il es un problème avec les types définis par l'utilisateur (comme (a + b) = c; avec une surcharge operator+ ).

J'ai toujours trouvé que la justification de cela était plutôt mince. Vous ne pouvez pas arrêter quelqu'un qui a l'intention d'écrire du code maladroit, et ce type particulier de coercition n'a aucun avantage réel à mon avis.

Avec C++11, il y a en fait une bonne part de préjudice cette idiotie : Le fait de retourner des valeurs en tant que const empêche l'optimisation des déplacements et doit donc être évité autant que possible. En fait, je considère maintenant cela comme un anti-modèle.

Voici un article tangentiellement lié concernant C++11.

0 votes

Que dire de dire qu'il est inutile de modifier la valeur de retour ? Par exemple, MyClass.isInitialized(). Si l'appelant modifie la valeur de retour, j'irais au devant d'une révision du code.

0 votes

@NoSenseEtAl : Qui suis-je pour savoir ce qui est inutile ? Pour ce qu'on en sait, l'opérateur d'affectation pourrait modifier un état global, ou commander une pizza....

0 votes

J'ai ajouté un exemple. à mon commentaire précédent. Et vous êtes le concepteur de la classe donc vous comprenez l'opérateur = de vos types de retour :D. Donc vous savez s'il est inutile pour l'appelant de changer le résultat de vos méthodes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X