29 votes

Pourquoi n'est-ce pas une erreur de compilation à affecter au résultat d'un appel substr?

Je viens de découvrir le plus déconcertant d'erreur et je ne comprends pas pourquoi le compilateur n'a pas de drapeau pour moi. Si j'ai écrit ce qui suit:

string s = "abcdefghijkl";
cout << s << endl;

s.substr(2,3) = "foo";
s.substr(8,1) = '.';
s.substr(9,1) = 4;
cout << s << endl;

Le compilateur n'a aucun problème que ce soit avec ce, et les instructions d'affectation ne semblent avoir aucun effet, ce qui est imprimé. En revanche,

s.front() = 'x';

a l'effet que j'avais prévu (depuis front renvoie une référence à un personnage) de l'évolution des sous-jacents de la chaîne, et

s.length() = 4;

aussi a l'effet attendu de générer une erreur de compilation se plaindre que vous ne pouvez pas affecter de quelque chose qui n'est pas une lvalue, car length renvoie un entier. (Eh bien, un size_t de toute façon.)

Alors... pourquoi sur la terre, n'est que le compilateur se plaint pas de l'affectation à la suite d'un substr appel? Elle renvoie une chaîne de valeur, pas une référence, donc il ne devrait pas être cessible, non? Mais j'ai essayé, en g++ (6.2.1) et clang++ (3.9.0), de sorte qu'il ne semble pas être un bug, et il ne semble pas être sensible à la version C++ (essayé 03, 11, 14).

45voto

Matt McNabb Points 14273

Le résultat de l' substr() est std::string objet temporaire -- c'est lui-même un exemplaire de la sous-chaîne, pas de vue sur la chaîne d'origine.

Étant un std::string objet, il a un opérateur d'affectation de la fonction, et votre code appelle cette fonction pour modifier l'objet temporaire.

C'est un peu surprenant, -- la modification d'un objet temporaire et à rejeter le résultat indique généralement une erreur de logique, donc, en général, il ya deux façons que les gens essaient d'améliorer la situation:

  1. De retour d'un const objet.
  2. Utilisation lvalue ref-qualifier sur l'opérateur d'affectation.

L'Option 1 serait la cause d'une erreur de compilation de votre code, mais il limite aussi valide des cas d'utilisation (par exemple, move-ing de la valeur de retour -- vous ne pouvez pas sortir d'un const chaîne de caractères).

Option 2 empêche l'opérateur d'affectation être utilisé à moins que le côté gauche est une lvalue. C'est une bonne idée à mon humble avis, bien que pas tout à fait d'accord; voir ce fil de discussion.

Dans tous les cas; lorsque ref-qualificatifs ont été ajoutés en C++11 , il a été proposé de revenir en arrière et changer les spécifications de tous les récipients à partir de C++03, mais cette proposition n'a pas été accepté (sans doute, dans le cas où il s'est cassé le code existant).

std::string a été conçu dans les années 1990 et a fait certains choix de conception qui semblent pauvres d'aujourd'hui, avec le recul, mais nous sommes coincés avec elle. Vous avez juste à comprendre le problème de la std::string lui-même, et peut-être éviter dans vos propres classes en utilisant ref-qualificatifs, ou de points de vue ou quoi que ce soit.

12voto

danieltm64 Points 431

La raison de votre code compile est parce qu'il est légal de C++. Voici un lien qui explique ce qu'il se passe.

https://accu.org/index.php/journals/227

C'est assez long, donc je vais citer la partie la plus pertinente:

Non-classe rvalues ne sont pas modifiables, ni peut avoir des cv qualifiés types (le cv-qualifications sont ignorés). Au contraire, la classe rvalues sont modifiables et peuvent être utilisés pour modifier un objet par l'intermédiaire de son les fonctions de membres. Ils peuvent aussi avoir des cv qualifiés types.

Donc, la raison pour laquelle vous ne pouvez pas attribuer de la valeur r est retourné en std::string::length est parce qu'il n'est pas une instance d'une classe, et la raison pour laquelle vous pouvez affecter à la rvalue retourné à partir de std::string::substr est parce qu'il est une instance d'une classe.

Je n'ai pas très bien pourquoi la langue est définie de cette façon, mais c'est comment il est.

7voto

Kirill Kobelev Points 6124

Regarde le code:

s.substr(2,3) = "foo";

L'appel à la fonction substr retourne une chaîne de caractères, c'est un objet et une valeur temporaire. Après que vous modifier cet objet (en fait par l'appel de la surcharge opérateur d'affectation de l' std::string de la classe). Cet objet temporaire n'est pas sauvé en quelque sorte. Compilateur détruit tout simplement la modification temporaire.

Ce code ne fait pas de sens. Vous pouvez demander, pourquoi le compilateur n'est pas de donner un avertissement? La réponse est que le compilateur peut-être mieux. Les compilateurs sont écrits par des gens, pas des dieux. Malheureusement, C++ permet de tonnes de différentes façons d'écrire insensée code ou le code qui déclenche un comportement indéfini. C'est l'un des aspects de cette langue. Il nécessite une meilleure connaissance et une plus grande attention de la programmeur par rapport à de nombreuses autres langues.

Je viens de vérifier avec MSVC 2015, le code:

std::string s1 = "abcdef";
s1.substr(1, 2) = "5678";

compile bien.

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