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

8 réponses

1 2 3
Avatar
Marc G
en fait, j'ai une solution, pas forcément très élégante, mais je vois pas
autre chose
pour éviter des classes dérivées de Property, j'enregistre le type de la
propriété dans une énumération

Property {
type le_type;
void* getter;
void* setter;
//etc...
};
et en fonction du type, je sais comment interpréter void*
Merci pour ton aide
Marc
Avatar
James Kanze
Jean-Marc Bourguet wrote:
"Marc G" writes:

Si tu veux une vérification dynamique:

TypeObjet z = xx->value();

devrait fonctionner et jeter une exception si ce n'est pas du bon type.


oui, mais
cout<<xx->value();
par exemple ne fonctionnera opas,


C'est normal, il faut avoir une idée du type à utiliser et << est
utilisable avec un tas de types.


Pour ce cas précis, je pense que la solution serait plutôt une
fonction virtuelle pure « display( std::ostread& ) const » dans
BaseProperty, avec un surcharge de << sur BaseProperty. Comme
ça, il n'a pas besoin de connaître le type.

--
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:
Mon problème d'une manière très générale.
Je développe un interpréteur et dans mon langage, je suis susceptib le de
manipuler des objets de différents type (peu importe le type) et avec des
propriétés (en lecture/écriture ou les 2)

je peux avoir par exemple un truc du genre

Type1 x;
puis
int y=x.size;

mon problème est de savoir rapidement et simplement si la
propriété size existe pour Type1 et d'obtenir "facilement"
une référence (constante le cas échéant) vers la donnée
membre correspondante (par exemple).

D'où mon idée de développer une classe Property, avec l'idée
de l'utiliser comme suit

// ne dispose que de données membres static
template <typename T>
class CObjectWithProperty {

template <typename U>
static void add_property(Property<U> const&); // "j'enregist re"
des propriétés

// etc...
};

je ne développe pas ici la classe CObjectWithProperty mais l'idée c 'est
qu'elle gère une liste de propriétés accessibles pour un objet, a vec leur
nom associé (CObjectWithProperty<Type1> classe associée aux objets Type1)
Seulement on ne construit malheureusement pas une liste avec des objets
hétérogènes.
Ma liste contient donc des objets de la seule classe de base de Propert y et
mon problème c'est d'obtenir un objet un objet T à partir de la cla sse
BaseProperty , classe de base de Property<T>



Ta liste contient des pointeurs, non les objets mêmes.

J'ai conscience que j'esplique pas trop bien, hélas...
Pour mieux comprendre, regarde le code 2 niveaux + haut.

En fait, le type ReturnValue convient comme lvalue (donc setter) en
redéfinissant l'opérateur = dans ReturnValue
Mon problème, c'est plutôt comme rvalue
ex :
Type1 x;
Type1 y;
int m= x.size*y.size // je fais quoi là :-(
-> je suis obligé de redéfinir tous les opérateurs
mais comme tu sais on peut définir ses propres opérateurs pour un t ype
d'objet et d'une manière générique, je suis incapable de le savoi r....
J'espère que tu m'as compris !


Oui, la classe ReturnValue n'adresse pas le vrai problème que tu
poses.


Il l'adresse en partie. Quand on veut supporter les expressions
arbitraire, ça devient plus difficile, parce qu'il faut définir
ce que ça veut dire, par exemple, de multiplier un std::string
par un int. En gros, il faut definir les opérateurs sur
BaseProperty, puis implémenter un espèce de double dispatch. La
solution classique du double dispatch, c'est expliqué dans
Coplien, mais elle suppose une hièrarchie fermée, ce qui n'est
pas le cas ici. À peu près la seule solution qui me vient à
l'esprit, c'est un std::map< std::pair< type_info const*,
type_info const* >, BaseProperty* (*)( BaseProperty const*,
BaseProperty const* ) > pour chaque opération supportée. Ce qui
pose le problème de la gestion de la mémoire. Peut-être en faire
un lettre-envéloppe de BaseProperty, de façon à pouvoir
l'utiliser comme type de valeur ?

Ensuite, évidemment, il faut écrire la fonction pour chaque
combinaison de types supportée, et l'inscrire dans le bon map.
(Ici, je conseillerais un bon script de génération du code.)

--
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
Jean-Marc Bourguet wrote:
"James Kanze" writes:

Jean-Marc Bourguet wrote:

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.


C'est une bonne façon de l'envisager, même si je crois que
l'implémentation la plus répandue correspond plutôt à :

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


Ça ne fonctionne que s'il est garanti que les descendant
retourne this, auquel cas, on n'a même pas besoin des membres
virtuels.


Je ne vois pas pourquoi. D'où est le problème ? Il faut que
type(), dans chaque descendant, renvoie quelque chose qui dérive
de la classe de base (ici, BaseProperty), c'est tout. Et c'est
ce qu'exige la norme.

--
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
Jean-Marc Bourguet
"James Kanze" writes:

Jean-Marc Bourguet wrote:
"James Kanze" writes:

Jean-Marc Bourguet wrote:

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.


C'est une bonne façon de l'envisager, même si je crois que
l'implémentation la plus répandue correspond plutôt à :

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


Ça ne fonctionne que s'il est garanti que les descendant
retourne this, auquel cas, on n'a même pas besoin des membres
virtuels.


Je ne vois pas pourquoi. D'où est le problème ? Il faut que
type(), dans chaque descendant, renvoie quelque chose qui dérive
de la classe de base (ici, BaseProperty), c'est tout. Et c'est
ce qu'exige la norme.


class Foo: public Property<int>
{
...
protected:
BaseProperty* type_() { return field_; }
...
Foo* field_;
};

Foo* ptr;
Property<int>* ptrPInt = ptr;
BaseProperty* ptrBase = ptr;
ptr->type() et ptrBase->type() pointe vers le même objet.
ptrPInt->type() ne le fait pas.

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
James Kanze
Jean-Marc Bourguet wrote:
"James Kanze" writes:

Jean-Marc Bourguet wrote:
"James Kanze" writes:

Jean-Marc Bourguet wrote:

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.


C'est une bonne façon de l'envisager, même si je crois que
l'implémentation la plus répandue correspond plutôt à :

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


Ça ne fonctionne que s'il est garanti que les descendant
retourne this, auquel cas, on n'a même pas besoin des membres
virtuels.


Je ne vois pas pourquoi. D'où est le problème ? Il faut que
type(), dans chaque descendant, renvoie quelque chose qui dérive
de la classe de base (ici, BaseProperty), c'est tout. Et c'est
ce qu'exige la norme.


class Foo: public Property<int>
{
...
protected:
BaseProperty* type_() { return field_; }
...
Foo* field_;
};

Foo* ptr;
Property<int>* ptrPInt = ptr;
BaseProperty* ptrBase = ptr;
ptr->type() et ptrBase->type() pointe vers le même objet.
ptrPInt->type() ne le fait pas.


Je crois qu'on parle de différentes choses. Quand un compilateur
implémente les valeurs de rétour co-variantes :

-- Il génère en fait la classe de base exactement comme il
l'aurait fait normalement -- en fait, il ne peut pas savoir
qu'il y a des rétour co-variant.

-- Quand il voit un rétour co-variant dans une classe dérivée,
il génère toujours la fonction telle qu'on l'a écrite. En
revanche, il génère aussi une fonction inaccessible à
l'utilisateur qui ressemble à ma version de la fonction
type_, ci-dessus, et c'est l'adresse de cette fonction qu'il
met dans le vtable.

-- Sauf, évidemment, s'il constate que la conversion est un
no-op ; dans de telles cas, il peut évidemment mettre
l'adresse de la fonction qui renvoie le type dérivé
directement dans le vtable.

L'implémentation typique, c'est bien que la fonction avec le nom
qu'a écrit l'utilisateur est la fonction qu'a écrit
l'utilisateur.

--
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
Jean-Marc Bourguet
"James Kanze" writes:

-- Il génère en fait la classe de base exactement comme il
l'aurait fait normalement -- en fait, il ne peut pas savoir
qu'il y a des rétour co-variant.


Nous sommes d'accord. Il est à noter que dans le code que je donne,
BaseProperty::type ne fait qu'appeler BaseProperty::type_.

-- Quand il voit un rétour co-variant dans une classe dérivée,
il génère toujours la fonction telle qu'on l'a écrite. En
revanche, il génère aussi une fonction inaccessible à
l'utilisateur qui ressemble à ma version de la fonction
type_, ci-dessus, et c'est l'adresse de cette fonction qu'il
met dans le vtable.


Comment se passe un appel où la fonction a été supplantée par un
descendant? Le code doit être capable de faire l'ajustement dans l'autre
sens aussi.

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
dieu.tout.puissant
Marc G wrote:
en fait, j'ai une solution, pas forcément très élégante, mais je vois pas
autre chose
pour éviter des classes dérivées de Property, j'enregistre le type de la
propriété dans une énumération

Property {
type le_type;
void* getter;
void* setter;
//etc...
};
et en fonction du type, je sais comment interpréter void*
Merci pour ton aide
Marc


De rien, mais je ne t'ai pas beaucoup aidé. :)

Il est vrai que savoir quelles sont le nom et le nombre propriétés à
l'avance ouvre des perspectives de design.

Par exemple :

Marc G wrote:
Type1 x;
puis
int y=x.size;



Je ne sais pas s'il y a des contraintes d'implémentations pour Type1,
mais :
Tu pourrais dire que Type1 contient un objet "property" de type
"ObjectProperty". Cet objet contient des données membres représentant
chacune un pointeur vers une propriété :
class ObjectProperty {
...
int* size;
TypeProp* prop;
... // listing de toutes les propriétés existantes correctement
typées
};

Le constructeur de cette classe utiliserait, par exemple, un vecteur
contenant chaque propriété (en tout cas un code les désignant
chacune) spécifiée pour la classe englobante (style Type1) afin
d'initialiser chaque propriété soit à NULL soit à un nouvel objet
du type de la propriété.

Exemple d'utilisation :

Type1 x;
Type2 y;
*x.property.size = 10;
*y.property.size = 50;
cout << *x.property.size;
int m = *x.property.size + *y.property.size;

et tu peux installer un gestionnaire d'erreur si la propriété est à
NULL (propriété non supportée par le type de l'objet).

Cette solution utilise la composition (Type1 est composé d'un objet
ObjectProperty) mais, si le contexte le permet, on pourrait utiliser
l'héritage, c'est-à-dire ObjectProperty ancêtre de tous les types
(style Type1). Dans ce cas on accède directement à la propriétés
(*x.size).


Je ne sais pas si ce type d'implémentation peut être utilisé dans
ton cas mais le but est que tu peux te servir de ta connaissance a
priori des propriétés, ce que tu avais fait d'ailleurs en utilisant
une énumération des types.


1 2 3