6
votes

C ++ Const-Référence Semantitique?

Considérez l'exemple d'application ci-dessous. Il démontre ce que j'appellerais une conception de classe imparfaite.

#include <iostream>

using namespace std;

struct B
{
 B() : m_value(1) {}

 long m_value;
};

struct A
{
 const B& GetB() const { return m_B; }

 void Foo(const B &b)
 {
  // assert(this != &b);
  m_B.m_value += b.m_value;
  m_B.m_value += b.m_value;
 }

protected:
 B m_B;
};

int main(int argc, char* argv[])
{
 A a;

 cout << "Original value: " << a.GetB().m_value << endl;

 cout << "Expected value: 3" << endl;
 a.Foo(a.GetB());

 cout << "Actual value: " << a.GetB().m_value << endl;

 return 0;
}


5 commentaires

Je ne vois pas pourquoi la valeur attendue est 3.


@Neil: Si vous ne connaissez pas les entrailles de a ...


La valeur attendue est 3 depuis A.Foo (B (A.Betb ()))) est égal à 3.


Pourquoi un exemple aussi complexe? Le même effet peut être vu dans cet extrait simple: int A = 1; int const & b = a; A = 4; STD :: COUT << B; .


Parce que les membres protégés à l'intérieur d'une classe peuvent être protégés de cela. C'est à quoi ressemble le message.


5 Réponses :


-1
votes

ma réponse stupide, je laisse ici ici si quelqu'un d'autre appartient à la même mauvaise idée:

Le problème est que je pense que l'objet mentionné sur n'est pas Const ( B const & vs const B & ), seule la référence est Const dans votre code. < / p>


5 commentaires

Non. Toute l'idée de mon échantillon de code est d'illustrer qu'il n'ya aucune garantie qu'un objet de consctone n'est pas modifié lors de la modification du présent pointeur.


B Const & vs const B & sont exactement les mêmes.


Const s'applique à tout ce qui est parti. const à droite est un sucre de syntaxe. iow const b const est identique à const b et B const


@Alex en essayant, j'ai compris que c'est exactement la même chose.


De plus, une référence de const ne signifie rien puisqu'il ne peut pas être "réinitialiser" à alias un autre objet.



20
votes

Évidemment, le programmeur est dupe de la Constance de B P>

Comme quelqu'un a dit une fois, vous continuez à utiliser ce mot. Je ne pense pas que cela signifie ce que vous pensez que cela signifie. Em> p>

const signifie que vous ne pouvez pas modifier la valeur. Cela ne signifie pas que la valeur ne peut pas changer. p>

Si le programmeur est conçu par le fait qu'un autre code d'autre peut changer quelque chose qu'ils ne peuvent pas, ils ont besoin d'une meilleure mise à la terre en aliasing. p>

Si le programmateur est dupe que le jeton 'const' sonne un peu comme «constante» mais signifie «lecture seulement», ils ont besoin d'une meilleure mise à la terre dans la sémantique de la langue de programmation qu'ils utilisent. P>

Donc, donc si vous avez un getter lequel Renvoie une référence Const, alors c'est un alias pour un objet que vous n'avez pas la permission de changer. Cela dit rien em> sur la question de savoir si sa valeur est immuable. P>


finalement, cela résume un manque d'encapsulation et ne pas appliquer la loi de Demeter. En général, n'utilisez pas l'état d'autres objets. Envoyez-leur un message pour leur demander d'effectuer une opération, qui peut (en fonction de leurs propres détails de mise en œuvre) Mutater leur état. P>

si vous faites b.m_value code> privé, alors vous Impossible d'écrire le foo code> que vous avez. Vous faites soit «code> foo code> dans: p> xxx pré>

ou, si vous souhaitez vous assurer que la valeur est constante, utilisez un P>

void Foo(B b)
{
    m_B.increment_by(b);
    m_B.increment_by(b);
}


3 commentaires

Au fait, Stroustrup voulait à l'origine le mot-clé lisonly , mais le comité C (!) Insisté sur la nommer IT Const .


C'est ce que l'échantillon illustre, oui. De toute évidence, je n'étais pas assez clair. Il doit y avoir des règles à suivre (lignes directrices) pour s'assurer que l'erreur dans le code ne peut pas arriver.


Si le comportement doit être basé sur les valeurs des arguments au point auquel la fonction est appelée, stockez ces valeurs dans des variables temporaires. Certains compilateurs soutiennent restreindre pour mettre en surbrillance aliasing. Mais il n'y a pas de directives simples pour éviter l'aliasing; Supposons que les valeurs puissent être aliasées à moins que le contraire.



1
votes

Le vrai problème ici est atomicité. La condition préalable de la fonction foo est que son argument ne change pas lors de l'utilisation.

si par ex. FOO avait été spécifié avec une valeur-argument I.S..O. argument de référence, aucun problème n'aurait montré.


2 commentaires

Exactement. Peut-être que de telles conditions préalables devraient être prises en compte lors du choix de renvoyer une référence ou une valeur?


@Kristoffer: Cette condition préalable est assez difficile à compter sur si vous passez un argument par-référence. Donc, soit vous construisez un système qui ne permet pas d'utiliser des arguments à utiliser (c'est-à-dire en verrouillant l'argument ou similaire), ou vous prenez un instantané de l'argument (si cela peut être fait atomiquement).



1
votes

Franchement, A :: FOO () me frotte le mauvais chemin plus que votre problème d'origine. De toute façon je le regarde, il doit être b :: foo () . Et à l'intérieur b :: foo () vérifier pour ceci ne serait pas aussi pur.

Sinon, je ne vois pas comment on peut spécifier une règle générique pour couvrir ce cas. Et garder les coéquipiers sain d'esprit.

Depuis l'expérience passée, je traiterais cela comme un viril simple et différencierait deux cas: (1) b est petit et (2) B est grand. Si B est petit, faites simplement simplement A :: getb () pour renvoyer une copie. Si B est grand, vous n'avez pas d'autre choix que de gérer le cas que les objets de B puissent être à la fois rvalue et lvalue dans la même expression.

Si vous avez de tels problèmes constamment, je dirais que la règle plus simple serait de toujours retourner une copie d'un objet au lieu d'une référence. Parce que très souvent, si l'objet est important, vous devez le gérer différemment du reste de toute façon.


0 commentaires

2
votes

Je propose une solution légèrement différente à celle-ci qui présente plusieurs avantages (en particulier dans un monde en augmentation multi-fileté). C'est une simple idée de suivre, et c'est de "commettre" vos modifications en dernier.

Pour expliquer via votre exemple, vous modifieriez simplement la classe "A" à: p>

struct A
{
 const B& GetB() const { return m_B; }

 void Foo(const B &b)
 {
  // copy out what we are going to change;
  int itm_value = m_b.m_value;

  // perform operations on the copy, not our internal value
  itm_value += b.m_value;
  itm_value += b.m_value;

  // copy over final results
  m_B.m_value = itm_value ;
 }

protected:
 B m_B;
};


0 commentaires