OVH Cloud OVH Cloud

Un petit problème de fonction virtuelle

28 réponses
Avatar
Marc G
j'ai le code suivant :

class BaseProperty {
public :
virtual BaseProperty* type(void) { return this;}
int value(void) { return -1;}
};

// représente une propriété d'un certain type
template <typename U>
class Property : public BaseProperty {
public :
Property(U* u) : _u(u) {}
virtual Property* type(void) { return this;}
U value(void) { return *_u; }
U* _u;
};

Mon problème :
j'ai lu qu'on pouvait changer le type de retour d'une fonction virtuelle
comme je le fais pour la fonction type
(et j'ai déjà utilisé avec succès cette faculté dans d'autres programmes)

si j'écris :
int y=50;
Property<int> x(&y);
BaseProperty *xx=&x;
alors
xx->type()->value() vaut -1 et pas 50
pourquoi ?
normalement, type() retourne un pointeur Property<int>* et l'appel de
value() devrait valoir 50
le pb, c'est mon compilo ou moi :-( ?
en plus, si je regarde typeid(xx->type()).name(), j'ai BaseProperty * comme
réponse
mais si je définit la fonction type() pour qu'elle retourne des références,
typeid(xx->type()).name() vaut Property
C'est à n'y rien comprendre...
Merci de vos lumières.
Marc

10 réponses

1 2 3
Avatar
Pierre Barbier de Reuille
Marc G wrote:
j'ai le code suivant :

class BaseProperty {
public :
virtual BaseProperty* type(void) { return this;}
int value(void) { return -1;}
};

// représente une propriété d'un certain type
template <typename U>
class Property : public BaseProperty {
public :
Property(U* u) : _u(u) {}
virtual Property* type(void) { return this;}
U value(void) { return *_u; }
U* _u;
};

Mon problème :
j'ai lu qu'on pouvait changer le type de retour d'une fonction virtuelle
comme je le fais pour la fonction type
(et j'ai déjà utilisé avec succès cette faculté dans d'autres p rogrammes)


Je ne sais pas où tu as pu utiliser cette "possibilité" mais elle
n'est certainement normalisée !! Elle est même innimplémentable ...

En effet, disons que tu as ce code :

struct A
{
virtual ~A() {}
virtual int f() { return 0; }
};

struct B : public A
{
virtual B& f() { return *this; }
};

int main()
{
B b;
A &a = b;
int c = a.f();
B& d = b.f();
}

Disons que si B::f est bien la version surchargée de A::f, comment c
et d comparent ? Ils devraient être identiques, mais en même temps B&
c'est pas convertible en int :-/

Par contre, le code est tout à fait valide et B::f cache A::f ...

Pierre


si j'écris :
int yP;
Property<int> x(&y);
BaseProperty *xx=&x;
alors
xx->type()->value() vaut -1 et pas 50
pourquoi ?
normalement, type() retourne un pointeur Property<int>* et l'appel de
value() devrait valoir 50
le pb, c'est mon compilo ou moi :-( ?
en plus, si je regarde typeid(xx->type()).name(), j'ai BaseProperty * com me
réponse
mais si je définit la fonction type() pour qu'elle retourne des réf érences,
typeid(xx->type()).name() vaut Property
C'est à n'y rien comprendre...
Merci de vos lumières.
Marc


Avatar
dieu.tout.puissant
Marc G wrote:
j'ai le code suivant :

class BaseProperty {
public :
virtual BaseProperty* type(void) { return this;}
int value(void) { return -1;}
};

// représente une propriété d'un certain type
template <typename U>
class Property : public BaseProperty {
public :
Property(U* u) : _u(u) {}
virtual Property* type(void) { return this;}
U value(void) { return *_u; }
U* _u;
};

Mon problème :
j'ai lu qu'on pouvait changer le type de retour d'une fonction virtuelle
comme je le fais pour la fonction type
(et j'ai déjà utilisé avec succès cette faculté dans d'autres p rogrammes)


Oui, car les types de retour de "type()" ont une relation d'héritage
publique. Property* est >>dérivée<< de BaseProperty*. C'est
uniquement dans ces cas-là que les types de retour (uniquement
pointeurs ou références) peuvent différer. Cela se nomme la
covariance des types de retour.


si j'écris :
int yP;
Property<int> x(&y);
BaseProperty *xx=&x;
alors
xx->type()->value() vaut -1 et pas 50
pourquoi ?
normalement, type() retourne un pointeur Property<int>* et l'appel de
value() devrait valoir 50
le pb, c'est mon compilo ou moi :-( ?
en plus, si je regarde typeid(xx->type()).name(), j'ai BaseProperty * com me
réponse


Il faut se mettre à la place du compilateur.
Le type statique de *xx est BaseProperty. Le fait que son type
dynamique soit Property change simplement les pointeurs des fonctions
virtuelles. Mais *xx est un BaseProperty, donc tout le typage défini
dans la classe BaseProperty (y compris le type de retour de ces
fonctions virtuelles) sera celui de xx.
Le type dynamique(et statique) de xx->type() sera donc BaseProperty *,
car xx->type() n'est qu'un pointeur. Tu l'obtiens en utilisant typeid :

typeid(xx->type().name() //BaseProperty *

Par contre, le type dynamique de l'objet pointé par xx->type() est
Property :
typeid(*xx->type().name() //Property

Donc si tu veux obtenir 50 et pas -1, il te suffit simplement de
déclarer "value()" virtual.

mais si je définit la fonction type() pour qu'elle retourne des réf érences,
typeid(xx->type()).name() vaut Property


Dans ce cas-là, xx->type() renvoie directement l'objet et non un
pointeur. Le type dynamique de cet objet est donc Property :
typeid(xx->type()).name() // Property

Par contre, si tu récupéres le type de l'adresse d'un objet
xx->type() (ceci étant + ou - une manière de récupérer le type
statique de xx->type()), tu obtiens BaseProperty * :
typeid(&xx->type()).name() // BaseProperty *

C'est bien le type statique de xx->type() puisque BaseProperty& est le
type de retour de "type()" dans la classe BaseProperty, dont xx est un
pointeur.


En espérant que mes propos soient clairs.

Avatar
dieu.tout.puissant
wrote:
Marc G wrote:
j'ai le code suivant :

class BaseProperty {
public :
virtual BaseProperty* type(void) { return this;}
int value(void) { return -1;}
};

// représente une propriété d'un certain type
template <typename U>
class Property : public BaseProperty {
public :
Property(U* u) : _u(u) {}
virtual Property* type(void) { return this;}
U value(void) { return *_u; }
U* _u;
};

Mon problème :
j'ai lu qu'on pouvait changer le type de retour d'une fonction virtuelle
comme je le fais pour la fonction type
(et j'ai déjà utilisé avec succès cette faculté dans d'autres programmes)


Oui, car les types de retour de "type()" ont une relation d'héritage
publique. Property* est >>dérivée<< de BaseProperty*. C'est
uniquement dans ces cas-là que les types de retour (uniquement
pointeurs ou références) peuvent différer. Cela se nomme la
covariance des types de retour.


si j'écris :
int yP;
Property<int> x(&y);
BaseProperty *xx=&x;
alors
xx->type()->value() vaut -1 et pas 50
pourquoi ?
normalement, type() retourne un pointeur Property<int>* et l'appel de
value() devrait valoir 50
le pb, c'est mon compilo ou moi :-( ?
en plus, si je regarde typeid(xx->type()).name(), j'ai BaseProperty * c omme
réponse


Il faut se mettre à la place du compilateur.
Le type statique de *xx est BaseProperty. Le fait que son type
dynamique soit Property change simplement les pointeurs des fonctions
virtuelles. Mais *xx est un BaseProperty, donc tout le typage défini
dans la classe BaseProperty (y compris le type de retour de ces
fonctions virtuelles) sera celui de xx.
Le type dynamique(et statique) de xx->type() sera donc BaseProperty *,
car xx->type() n'est qu'un pointeur. Tu l'obtiens en utilisant typeid :

typeid(xx->type().name() //BaseProperty *

Par contre, le type dynamique de l'objet pointé par xx->type() est
Property :
typeid(*xx->type().name() //Property

Donc si tu veux obtenir 50 et pas -1, il te suffit simplement de
déclarer "value()" virtual.

J'ai oublié de préciser que, dans ton code, tu peux déclarer

"value()" en virtual car x est un Property<int>, donc "value()" aura le
même type de retour dans Property et BaseProperty. Si tu utilises un
Property<double> par exemple, tu ne pourras plus déclarer "value()" en
fonction virtuelle.


Avatar
Jean-Marc Bourguet
"Pierre Barbier de Reuille" writes:

Marc G wrote:
j'ai le code suivant :

class BaseProperty {
public :
virtual BaseProperty* type(void) { return this;}
int value(void) { return -1;}
};

// représente une propriété d'un certain type
template <typename U>
class Property : public BaseProperty {
public :
Property(U* u) : _u(u) {}
virtual Property* type(void) { return this;}
U value(void) { return *_u; }
U* _u;
};

Mon problème :
j'ai lu qu'on pouvait changer le type de retour d'une fonction virtuelle
comme je le fais pour la fonction type
(et j'ai déjà utilisé avec succès cette faculté dans d'autres programmes)


Je ne sais pas où tu as pu utiliser cette "possibilité" mais elle
n'est certainement normalisée !! Elle est même innimplémentable ...


Le retour covariant est normalisé et implémentable. Mais il fait moins que
ce que pense Pierre.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
Jean-Marc Bourguet
"Marc G" writes:

j'ai le code suivant :

class BaseProperty {
public :
virtual BaseProperty* type(void) { return this;}
int value(void) { return -1;}
};

// représente une propriété d'un certain type
template <typename U>
class Property : public BaseProperty {
public :
Property(U* u) : _u(u) {}
virtual Property* type(void) { return this;}
U value(void) { return *_u; }
U* _u;
};

Mon problème :
j'ai lu qu'on pouvait changer le type de retour d'une fonction virtuelle
comme je le fais pour la fonction type
(et j'ai déjà utilisé avec succès cette faculté dans d'autres programmes)
si j'écris :
int yP;
Property<int> x(&y);
BaseProperty *xx=&x;
alors
xx->type()->value() vaut -1 et pas 50
pourquoi ?


Le retour covariant n'est qu'un moyen d'écrire

class BaseProperty {
public:
BaseProperty* type() { return type_(); }
protected:
virtual BaseProperty* type_() { return this; }
};

template <typename U>
class Property: public BaseProperty {
public:
Property* type() { return static_cast<Property*>(type_()); }
protected:
BaseProperty* type_() { return this; }
};

Et ne change rien à la vision statique du code.

normalement, type() retourne un pointeur Property<int>* et l'appel de
value() devrait valoir 50
le pb, c'est mon compilo ou moi :-( ?
en plus, si je regarde typeid(xx->type()).name(), j'ai BaseProperty * comme
réponse


Normal, c'est bien le type du pointeur. Essaye
typeid(*xx->type()).name()
pour avoir le type pointé.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Marc G
struct A
{
virtual ~A() {}
virtual int f() { return 0; }
};

struct B : public A
{
virtual B& f() { return *this; }
};

c'est uniquement valable quand f retourne un pointeur ou une référence sur
le type de la classe dans la hiérarchie
dans ton exemple, il y a conflit de fonctions virtuelles...
Marc
Avatar
Marc G
J'ai oublié de préciser que, dans ton code, tu peux déclarer
"value()" en virtual car x est un Property<int>, donc "value()" aura le
même type de retour dans Property et BaseProperty. Si tu utilises un
Property<double> par exemple, tu ne pourras plus déclarer "value()" en
fonction virtuelle.


oui, et c'est bien là mon problème. C'est précisement ce cas qui
m'intéresse.

Avatar
James Kanze
Marc G wrote:
j'ai le code suivant :

class BaseProperty {
public :
virtual BaseProperty* type(void) { return this;}
int value(void) { return -1;}
};

// représente une propriété d'un certain type
template <typename U>
class Property : public BaseProperty {
public :
Property(U* u) : _u(u) {}
virtual Property* type(void) { return this;}
U value(void) { return *_u; }
U* _u;
};

Mon problème :
j'ai lu qu'on pouvait changer le type de retour d'une fonction
virtuelle comme je le fais pour la fonction type (et j'ai déjà
utilisé avec succès cette faculté dans d'autres programmes)


Certes, mais le typage reste statique. Tu n'as l'avantage du
type dérivé que si tu utilises des expressions de type Property.

si j'écris :
int yP;
Property<int> x(&y);
BaseProperty *xx=&x;
alors
xx->type()->value() vaut -1 et pas 50
pourquoi ?


Parce que value n'est pas virtuelle. L'expression xx->type()
renvoie bien un BaseProperty*, quelque soit le type de rétour
declaré de la fonction réelement appelée. (L'expression x.type()
renvoie bien un Property<int>*, en revanche.) Et puisque
BaseProperty::value() n'est pas virtuelle, c'est bien
BaseProperty::value() qui serait appelée.

Si tu y penses un peu, je crois que tu te rendras compte que ça
ne peut pas être autrement. Il faut bien que le compilateur
connaisse le type de l'expression xx->type(), afin de savoir où
chercher la fonction value() (dans ce cas précis). Et la seule
chose que le compilateur connaît, c'est bien le type static de
l'expression ; il ne peut pas savoir que xx va en fait désigner
un Property<int>. Il est donc obligé de chercher la fonction
dans BaseProperty, et de traiter la valeur de rétour de type
comme étant BaseProperty*.

normalement, type() retourne un pointeur Property<int>* et l'appel de
value() devrait valoir 50


dynamic_cast< Property< int >* >( xx )->type()

renvoie un pointeur Property< int >*. xx->type() renvoie un
pointeur BaseProperty*, même si c'est exactement la même
fonction qui est appelée.

le pb, c'est mon compilo ou moi :-( ?
en plus, si je regarde typeid(xx->type()).name(), j'ai BaseProperty * com me
réponse


Parce que le type renvoyé, c'est bien un BaseProperty*. Le type
dynamique qu'il pointe, en revanche, est un Property<int> ; si
tu fais typeid( *xx->type() ).name(), tu auras bien
Property<int>.

C'est exactement le même comportement que ce que tu aurais avec
xx tout court : typeid(xx).name() est BaseProperty*, mais
typeid(*xx).name() est Property<int>.

mais si je définit la fonction type() pour qu'elle retourne des réf érences,
typeid(xx->type()).name() vaut Property


Parce qu'il y a déréferencement automatique des références.

--
James Kanze (GABI Software) email:
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
James Kanze
Pierre Barbier de Reuille wrote:
Marc G wrote:
j'ai le code suivant :

class BaseProperty {
public :
virtual BaseProperty* type(void) { return this;}
int value(void) { return -1;}
};

// représente une propriété d'un certain type
template <typename U>
class Property : public BaseProperty {
public :
Property(U* u) : _u(u) {}
virtual Property* type(void) { return this;}
U value(void) { return *_u; }
U* _u;
};

Mon problème :
j'ai lu qu'on pouvait changer le type de retour d'une fonction virtuelle
comme je le fais pour la fonction type
(et j'ai déjà utilisé avec succès cette faculté dans d'autres programmes)


Je ne sais pas où tu as pu utiliser cette "possibilité" mais elle
n'est certainement normalisée !! Elle est même innimplémentable ...


Elle est bien normalisée, et elle est même implémenté, au moins
par g++ (et certainement par la plupart des autres
compilateurs). Je connais au moins deux implémentations
possibles.

En effet, disons que tu as ce code :

struct A
{
virtual ~A() {}
virtual int f() { return 0; }
};

struct B : public A
{
virtual B& f() { return *this; }
};


Mais ce n'est pas ce qu'il a. Il a le cas où la valeur de retour
dans la classe de base est un pointeur à une base, et la valeur
de retour dans la dérivée est une dérivée. En principe, pour que
ça puisse marcher, il faut qu'il existe une conversion du type
de rétour dans la classe dérivée vers le type de rétour dans la
classe de base. Dans le cas de C++, la norme ne l'autorise que
si les deux types de rétour sont tous les deux des pointeurs ou
des références, et que le type désigné dans la classe dérivée
dérive du type désigné dans la classe de base.

--
James Kanze (GABI Software) email:
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
James Kanze
wrote:
Marc G wrote:
j'ai le code suivant :

class BaseProperty {
public :
virtual BaseProperty* type(void) { return this;}
int value(void) { return -1;}
};

// représente une propriété d'un certain type
template <typename U>
class Property : public BaseProperty {
public :
Property(U* u) : _u(u) {}
virtual Property* type(void) { return this;}
U value(void) { return *_u; }
U* _u;
};

Mon problème :
j'ai lu qu'on pouvait changer le type de retour d'une fonction virtuelle
comme je le fais pour la fonction type
(et j'ai déjà utilisé avec succès cette faculté dans d'autres programmes)


Oui, car les types de retour de "type()" ont une relation d'héritage
publique. Property* est >>dérivée<< de BaseProperty*. C'est
uniquement dans ces cas-là que les types de retour (uniquement
pointeurs ou références) peuvent différer. Cela se nomme la
covariance des types de retour.

si j'écris :
int yP;
Property<int> x(&y);
BaseProperty *xx=&x;
alors
xx->type()->value() vaut -1 et pas 50
pourquoi ?
normalement, type() retourne un pointeur Property<int>* et l'appel de
value() devrait valoir 50
le pb, c'est mon compilo ou moi :-( ?
en plus, si je regarde typeid(xx->type()).name(), j'ai BaseProperty * c omme
réponse


Il faut se mettre à la place du compilateur.
Le type statique de *xx est BaseProperty. Le fait que son type
dynamique soit Property change simplement les pointeurs des fonctions
virtuelles.


En supposant que c'est comme ça que le compilateur implémente
les fonctions virtuelles. L'important, évidemment, c'est que de
telles expressions ont effectivement deux types, un type
statique, connu du compilateur, et un type dynamique, qui peut
varié lors de l'exécution. Ensuite, il faut savoir où le
compilateur utilise directement le type statique, et où il fait
une résolution dynamique. Et qu'en l'occurance, à l'encontre du
SmallTalk, le compilateur utilise toujours le type statique pour
la récherche des noms. (Sinon, l'expression serait légale même
si BaseProperty ne contenait pas de fonction value. Mais comment
alors est-ce que le compilateur pourrait savoir si le type
dynamique contenait une fonction du nom value ?)

[...]
Donc si tu veux obtenir 50 et pas -1, il te suffit simplement de
déclarer "value()" virtual.


Sauf qu'évidemment, tout le but qu'il poursuit, c'est de
permettre au type de rétour de value() de varier de façon
arbitraire.

En fait, puisqu'il s'agit d'une dépendance dynamique, il faut
bien qu'il implémente quelque chose qui détermine le type
dynamiquement, et qui prévoir soit une erreur soit une
conversion supplémentaire s'il démande par exemple un double, et
le type dynamique est Property<int>. Je verrai bien quelque
chose du genre :

template< typename T > class Property ;

class BaseProperty
{
public:
class ReturnValue
{
public:
ReturnValue( BaseProperty* owner )
: myOwner( owner )
{
}

template< typename T >
operator T() const
{
return dynamic_cast< Property< T > const& >( *myOwner
).value() ;
}

template< typename T >
void operator=( T const& rhs ) const
{
dynamic_cast< Property< T >& >( *myOwner ).value() =
rhs ;
}

private:
BaseProperty* myOwner ;
} ;

ReturnValue value() ;
} ;

template< typename T >
class Property : public BaseProperty
// comme il a fait, plus ou moins, mais sans la fonction
// type.

Ce n'est pas parfait : pour faire marcher &xx->value(), il
faudrait surcharger l'operator& de ReturnValue pour qu'il
renvoie encore un proxy, et autant que je sache, ce n'est même
pas possible d'implémenter xx->value().unElementDeStruct. (On
peut, en revanche, supporter xx->value()->unElementDeStruct en
surchargeant l'operator-> de ReturnValue.)

--
James Kanze (GABI Software) email:
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


1 2 3