Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Creer un accesseur const a partir d'un non const

14 réponses
Avatar
Aurélien REGAT-BARREL
Bonjour à tous,
je cherche à appeler explicitement la version const d'une fonction membre.
C'est un cas un peu tordu que je résume ici:
J'ai 2 classes abstraites A et B, A utilise B, mais B est fournie par une
classe fille de A:

class B
{
public:
virtual string GetName() const = 0;
};

class A
{
public:
string GetName() const
{
return this->GetB().GetName();
}

protected:
virtual B & GetB() = 0;
};

GetB() est la fonction que les classes filles doivent supplanter afin de
fournir un B à A. Ok ? Bien.

Dans l'exemple ci-dessus, const GetName() qui appelle non const GetB() ne
vous aura pas échappé. Et voilà mon problème. Actuellement je contourne ça
ainsi :

protected:
virtual B & GetB() = 0;
virtual const B & GetB() const = 0;
};

Mais j'aimerais bien arriver à frabriquer l'accesseur const à partir du non
const et ainsi réduire le nombre de fonctions virtuelles pures (qui sont
identiques ici vu de la classe fille). Est-ce possible (propement, sans
const_cast, à moins qu'il soit justifié) ?
Merci.

--
Aurélien REGAT-BARREL

10 réponses

1 2
Avatar
Arnaud Meurgues
Aurélien REGAT-BARREL wrote:

class A
{
public:
string GetName() const
{
return this->GetB().GetName();
}
protected:
virtual B & GetB() = 0;
};
[...]

Mais j'aimerais bien arriver à frabriquer l'accesseur const à partir du non
const et ainsi réduire le nombre de fonctions virtuelles pures (qui sont
identiques ici vu de la classe fille). Est-ce possible (propement, sans
const_cast, à moins qu'il soit justifié) ?


Qu'appelles-tu un const_cast justifié ?
De deux choses l'une :
1) la fonction GetB() peut changer l'objet A
Dans ce cas, il est logique de ne pas appeler GetB() dans une
fonction const de A.
2) la fonction GetB() ne change pas l'objet A mais permet
indirectement de le changer dans le cas où l'on change le B retourné par
la fonction.
Dans ce cas, si l'on n'appelle que des fonctions const sur B, alors
on est assuré de ne pas changer A.

Dans le cas 2), on peut faire un const_cast :

string GetName() const
{
return (const_cast<A*>(this))->GetB().GetName();
}

Mais par ailleurs, si quelqu'un peut fournir son propre GetB() pour une
sous-classe, rien ne l'empêcherait de modifier l'objet dans un GetB()
non const. Donc, il est loin d'être certain que ce soit la bonne solution.

--
Arnaud
(Supprimez les geneurs pour me répondre)

Avatar
Aurélien REGAT-BARREL
Qu'appelles-tu un const_cast justifié ?
Ben j'ai tendance à considérer que tout ce qui est const_cast /

reinterpret_cast est généralement une marque de mauvaise conception (hormis
contraintes telles que utilisation d'une lib c void *).

De deux choses l'une :
1) la fonction GetB() peut changer l'objet A
Dans ce cas, il est logique de ne pas appeler GetB() dans une
fonction const de A.


Non, c'est un stupide accesseur sur le B de la classe fille.

2) la fonction GetB() ne change pas l'objet A mais permet
indirectement de le changer dans le cas où l'on change le B retourné par
la fonction.


Oui. GetB renvoie simplement un B qui peut être modifié, et l'est à certains
endroits. En fait au début j'étais parti sur un pointeur/référence que la
classe fille devait passer au constructeur, et je me suis dit qu'ainsi
c'était plus mieux bien :

class AImpl : public A
{
protected:
B & GetB() { return this->b; }

private:
BImpl b;
};

Dans ce cas, si l'on n'appelle que des fonctions const sur B, alors
on est assuré de ne pas changer A.


Tout à fait.

Dans le cas 2), on peut faire un const_cast :

string GetName() const
{
return (const_cast<A*>(this))->GetB().GetName();
}


Pour éviter de pourrir de const_cast j'ai voulu surcharger GetB en const.

Mais par ailleurs, si quelqu'un peut fournir son propre GetB() pour une
sous-classe, rien ne l'empêcherait de modifier l'objet dans un GetB()
non const. Donc, il est loin d'être certain que ce soit la bonne solution.


Moui. En plus const_cast c'est pas beau :-)

--
Aurélien REGAT-BARREL

Avatar
Loïc Joly
Aurélien REGAT-BARREL wrote:
Bonjour à tous,
je cherche à appeler explicitement la version const d'une fonction membre.
[...]


virtual B & GetB() = 0;
virtual const B & GetB() const = 0;

Mais j'aimerais bien arriver à frabriquer l'accesseur const à partir du non
const et ainsi réduire le nombre de fonctions virtuelles pures (qui sont
identiques ici vu de la classe fille). Est-ce possible (propement, sans
const_cast, à moins qu'il soit justifié) ?


Est-ce que le code suivant répond à ton problème ?

class B;

class A
{
public:
B* getB() {return doGetB();}
B const *getB() const {return doGetB();}
private:
virtual B* doGetB() const = 0;
};


--
Loïc

Avatar
kanze
"Aurélien REGAT-BARREL" wrote in
message news:<41a3428f$0$31404$...

je cherche à appeler explicitement la version const d'une fonction
membre. C'est un cas un peu tordu que je résume ici:
J'ai 2 classes abstraites A et B, A utilise B, mais B est fournie par
une class B

{
public:
virtual string GetName() const = 0;
};

class A
{
public:
string GetName() const
{
return this->GetB().GetName();
}

protected:
virtual B & GetB() = 0;
};

GetB() est la fonction que les classes filles doivent supplanter afin
de fournir un B à A. Ok ? Bien.

Dans l'exemple ci-dessus, const GetName() qui appelle non const GetB()
ne vous aura pas échappé. Et voilà mon problème. Actuellement je
contourne ça ainsi :

protected:
virtual B & GetB() = 0;
virtual const B & GetB() const = 0;
};

Mais j'aimerais bien arriver à frabriquer l'accesseur const à partir
du non const et ainsi réduire le nombre de fonctions virtuelles pures
(qui sont identiques ici vu de la classe fille). Est-ce possible
(propement, sans const_cast, à moins qu'il soit justifié) ?


La première question concerne le B, qui est bien un objet distinct de A.
Est-ce qu'il y a une raison pour ne pas avoir simplement la seule
fonction :

virtual B& GetB() const = 0 ;

Ensuite, il faut poser la question que fait GetB ? Est-ce qu'il est
vraiment genant qu'elle existe en deux versions (comme tu fait
maintenant.)

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Falk Tannhäuser
wrote:
La première question concerne le B, qui est bien un objet distinct de A.
Est-ce qu'il y a une raison pour ne pas avoir simplement la seule
fonction :

virtual B& GetB() const = 0 ;


Effectivement, si l'état du B retourné par référence ne fait pas partie
de l'état observable de l'objet de classe A sur lequel GetB() est appel ée,
ça me paraît la bonne solution.


Au cas contraire, il faut réellement deux fonctions, et il me paraît le
plus judicieux d'implémenter la version non-const en se servant de la
version const :

virtual B const& GetB() const = 0;

B& GetB() // non virtuelle
{
A const* const_this = this;
return const_cast<B&>(const_this->GetB());
// On pourrait écrire cela en une seule ligne mais c'est moins joli :
//return const_cast<B&>(const_cast<A const*>(this)->GetB());
}

Ici, le const_cast me semble pleinement "justifie" :
La version non-const de GetB() ne peut être appelée que sur un objet A
non-const (sauf const_cast de la part de l'utilisateur), et l'objet B
fait, au moins d'un point vu logique, partie de l'objet A. Il est donc
lui aussi non-const, et par conséquent, on a le droit de le modifier
après avoir crée un chemin d'accès non-const (moyennement const_cas t<B&)(...)).


Reste à savoir quel est réellement le type d'association entre l'obje t A et
l'objet B retourné par A::GetB() - ce qui est une question de conceptio n
plutôt que de C++, et seul l'OP saura y répondre.

Falk

Avatar
Aurélien REGAT-BARREL
La première question concerne le B, qui est bien un objet distinct de A.
Est-ce qu'il y a une raison pour ne pas avoir simplement la seule
fonction :

virtual B& GetB() const = 0 ;


Pas possible, car GetB() est supplantée de cette manière :

class A; // abstraite
class B; // abstraite

class B1 : public B
{
};

class A_B1 : public A
{
protected:
B & GetB() { return this->b; }

private:
B1 b; // A_B2 utilise un B2
}

Pour info une classe A template c'est pas possible car je doit spécialiser
A_Bx en fonction du Bx utilisé.

Ensuite, il faut poser la question que fait GetB ? Est-ce qu'il est
vraiment genant qu'elle existe en deux versions (comme tu fait
maintenant.)


GetB() sert juste à fournir à la classe de base le B utilisé par la classe
fille.
C'est pas la mort non. Gênant c'est le mot. Car en fait il y a plusieurs B
dans A (de types possiblement différents, [possiblement ça existe ?]), et
donc ça multiplie par deux le nombre de fonctions à supplanter, fonctions
strictement identiques au const près.
Donc je vis très bien avec mes deux fonctions, je voudrais juste quelque
chose de plus élégant...
Je pensais qu'il y avait une astuce. Etant donné qu'une fonction n'est pas
constante car elle renvoie une donnée membre non constante, si on lui fait
renvoyer cette même donnée membre constante je pensais naïvement qu'on
pouvait la rendre constante. Mais vu que le compilo ne s'attache qu'à la
signature d'une fonction et non à ce qu'elle fait, je réalise que c'est pas
possible.

--
Aurélien REGAT-BARREL

Avatar
Aurélien REGAT-BARREL
virtual B& GetB() const = 0 ;


Effectivement, si l'état du B retourné par référence ne fait pas partie
de l'état observable de l'objet de classe A sur lequel GetB() est appelée,
ça me paraît la bonne solution.


Sauf que B en fait partie :-/

Au cas contraire, il faut réellement deux fonctions, et il me paraît le
plus judicieux d'implémenter la version non-const en se servant de la
version const :


Je pensais faire le contraire : implémenter le const à partir du non const.
Cela me parraît dangereux de casser ce const. Vu de la classe fille on
pourrait croire que GetB() ne modifie pas le B fourni. Hors tralala c'est le
cas.
Dans l'autre sens on ne fait que forcer le compilateur à considérer une
fonction membre comme const, chose qu'elle est effectivement.

La version non-const de GetB() ne peut être appelée que sur un objet A
non-const (sauf const_cast de la part de l'utilisateur), et l'objet B
fait, au moins d'un point vu logique, partie de l'objet A. Il est donc
lui aussi non-const, et par conséquent, on a le droit de le modifier
après avoir crée un chemin d'accès non-const (moyennement
const_cast<B&)(...)).


Je vois ce que tu veux dire.
Comme non const GetB est appelé sur un A qui doit être non const (et qui est
donc modifiable),
on a le droit au const_cast.
Le problème c'est que le B est possédé par une classe fille, et vue de
celle-ci elle donne accès à sa classe mère un accès en lecture seule à sa
donnée membre. Hors la classe mère a bel et bien besoin de modifier le B
donné.

En fait, je cherche en quelque sorte à avoir une donnée membre virtuelle
pure :-)

--
Aurélien REGAT-BARREL


Avatar
Aurélien REGAT-BARREL
Est-ce que le code suivant répond à ton problème ?

class B;

class A
{
public:
B* getB() {return doGetB();}
B const *getB() const {return doGetB();}
private:
virtual B* doGetB() const = 0;
};


Si je peux écrire doGetB const qui me renvoie un B non const, plus besoin de
doGetB(), j'ai mon GetB() ;-)
C'est le doGetB() que je cherche à avoir, avec le B renvoyé membre de la
classe fille de A.

--
Aurélien REGAT-BARREL

Avatar
drkm
"Aurélien REGAT-BARREL" writes:

Est-ce que le code suivant répond à ton problème ?

class B;

class A
{
public:
B* getB() {return doGetB();}
B const *getB() const {return doGetB();}
private:
virtual B* doGetB() const = 0;
};


Si je peux écrire doGetB const qui me renvoie un B non const, plus besoin de
doGetB(), j'ai mon GetB() ;-)
C'est le doGetB() que je cherche à avoir, avec le B renvoyé membre de la
classe fille de A.


Donc, tu veux modifier une variable membre d'un objet constant ?

--drkm


Avatar
Loïc Joly
Aurélien REGAT-BARREL wrote:

Est-ce que le code suivant répond à ton problème ?

class B;

class A
{
public:
B* getB() {return doGetB();}
B const *getB() const {return doGetB();}
private:
virtual B* doGetB() const = 0;
};



Si je peux écrire doGetB const qui me renvoie un B non const, plus besoin de
doGetB(), j'ai mon GetB() ;-)


Pas tout à fait. Implémenter un tel doGetB est facile (il suffit que B
soit inclu par un pointeur dans le dérivé de A). Mais un tel doGetB
viole la const correctness "philosophique" de la classe, d'où l'intérêt
de l'encapsuler dans deux fonctions qui rétablissent cette
const-correctness.

Le code suivant compile :

class B{};

class A
{
public:
B* getB() {return doGetB();}
B const *getB() const {return doGetB();}
private:
virtual B* doGetB() const = 0;
};

class A1 : public A
{
private:
virtual B* doGetB() const {return myB;}
B *myB;
};


int main()
{
A* a = new A1;
A const * ac = new A1;
B* b = a->getB();
B const *bc = ac->getB();
}

--
Loïc


1 2