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

pb d'opérateurs virtuels

17 réponses
Avatar
Marc G
Mon compilateur m'oblige à redéfinir l'opérateur virtuel = dans les classes
dérivées. Est-ce normal ?
Par contre, pour les autres opérateurs virtuels, ça passe très bien (ex :
operator +=())
L'imbrication de mes classes est un peu compliquée, mais j'ai
schématiquement :

Dans la classe de base
virtual CVariant& operator=(CVariant const& right) { /* */ return *this; }

Dans certaines classes dérivées, cette définition me va très bien, mais le
compilateur me dit que operator= n'est pas définit.
Un copier/coller de la définition de la classe de base et ça marche.
Mais pour operator+=, qui modifie lui aussi l'objet, ça marche. Et là je
vois pas pourquoi dans un cas et pas dans l'autre ...
Il y a une "règle" là dessus ?
Marc

10 réponses

1 2
Avatar
James Kanze
Marc G wrote:
Mon compilateur m'oblige à redéfinir l'opérateur virtuel = dans l es classes
dérivées. Est-ce normal ?


Je ne comprends pas trop ce que tu essaies de dire. Le
compilateur ne t'oblige jamais à définir un opérateur
d'affectation ; si tu n'en définis pas, il en définit un pour
toi.

Ce auquel peut-être tu fais référence, c'est le fait que la
signature de celui que génère le compilateur n'est pas identique
à celui de la classe de base. Il ne le supplante donc pas.

Mais dans la pratique, qu'est-ce que tu cherches à faire ?
Parce que l'affectatin et l'héritage font mauvais menage. En
général, quand une classe est conçue pour servir de classe de
base (fonctions virtuelles, etc.), j'interdis l'affectation, en
dérivant de boost::noncopiable ou en déclarant un opérateur
d'affectation privé, sans implémentation.

Par contre, pour les autres opérateurs virtuels, ça passe très bien (ex :
operator +=())


C-à-d ?

L'imbrication de mes classes est un peu compliquée, mais j'ai
schématiquement :

Dans la classe de base
virtual CVariant& operator=(CVariant const& right) { /* */ return *thi s; }

Dans certaines classes dérivées, cette définition me va très bien,


Vraiment ?

mais le
compilateur me dit que operator= n'est pas définit.


C-à-d. Il y a un message d'erreur ? Lequel ?

Un copier/coller de la définition de la classe de base et ça marche.


Un copier/coller de la définition de la classe de base ne doit
rien changer, parce que la définition de la classe de base n'est
pas un opérateur d'affection par copie de la classe dérivée.

Mais pour operator+=, qui modifie lui aussi l'objet, ça marche. Et l à je
vois pas pourquoi dans un cas et pas dans l'autre ...
Il y a une "règle" là dessus ?


Il y a une règle, que l'opérateur d'affectation est un peu
spécial, en ce que le compilateur peut en générer un par défaut,
et qu'il faut donc qu'il soit membre. Ce que tu fais dans
l'opérator +=, ou même s'il existe, en revanche, le compilateur
s'en fout.

Mais je serais intéressé à voir du code qui présente le problème
dont tu parles. De même que je serais intéressé à voir un cas
où un opérator d'affectation virtuel n'est pas une erreur de
conception.

--
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
dieu.tout.puissant
On 23 nov, 08:52, "Marc G" wrote:
Mon compilateur m'oblige à redéfinir l'opérateur virtuel = dans l es classes
dérivées. Est-ce normal ?
Par contre, pour les autres opérateurs virtuels, ça passe très bien (ex :
operator +=())
L'imbrication de mes classes est un peu compliquée, mais j'ai
schématiquement :

Dans la classe de base
virtual CVariant& operator=(CVariant const& right) { /* */ return *thi s; }

Dans certaines classes dérivées, cette définition me va très bien , mais le
compilateur me dit que operator= n'est pas définit.
Un copier/coller de la définition de la classe de base et ça marche.
Mais pour operator+=, qui modifie lui aussi l'objet, ça marche. Et l à je
vois pas pourquoi dans un cas et pas dans l'autre ...
Il y a une "règle" là dessus ?
Marc


Oui. Une classe dérivée possède toutes les fonctions membres de sa
classe de base excepté 3 :
- le constructeur
- le destructeur
- opérator=.

Avatar
James Kanze
wrote:
On 23 nov, 08:52, "Marc G" wrote:
Mon compilateur m'oblige à redéfinir l'opérateur virtuel = dans les classes
dérivées. Est-ce normal ?
Par contre, pour les autres opérateurs virtuels, ça passe très bi en (ex :
operator +=())
L'imbrication de mes classes est un peu compliquée, mais j'ai
schématiquement :

Dans la classe de base
virtual CVariant& operator=(CVariant const& right) { /* */ return *t his; }

Dans certaines classes dérivées, cette définition me va très bi en, mais le
compilateur me dit que operator= n'est pas définit.
Un copier/coller de la définition de la classe de base et ça marche.
Mais pour operator+=, qui modifie lui aussi l'objet, ça marche. Et là je
vois pas pourquoi dans un cas et pas dans l'autre ...
Il y a une "règle" là dessus ?


Oui. Une classe dérivée possède toutes les fonctions membres de sa
classe de base excepté 3 :
- le constructeur
- le destructeur
- opérator=.


Il les possède aussi. Seulement, elles sont cachées par celles
généraient par le compilateur.

--
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
dieu.tout.puissant
On 23 nov, 22:16, "James Kanze" wrote:
wrote:
On 23 nov, 08:52, "Marc G" wrote:
Mon compilateur m'oblige à redéfinir l'opérateur virtuel = da ns les classes
dérivées. Est-ce normal ?
Par contre, pour les autres opérateurs virtuels, ça passe très bien (ex :
operator +=())
L'imbrication de mes classes est un peu compliquée, mais j'ai
schématiquement :
Dans la classe de base
virtual CVariant& operator=(CVariant const& right) { /* */ return *this; }
Dans certaines classes dérivées, cette définition me va très bien, mais le
compilateur me dit que operator= n'est pas définit.
Un copier/coller de la définition de la classe de base et ça marc he.
Mais pour operator+=, qui modifie lui aussi l'objet, ça marche. E t là je
vois pas pourquoi dans un cas et pas dans l'autre ...
Il y a une "règle" là dessus ?
Oui. Une classe dérivée possède toutes les fonctions membres de sa

classe de base excepté 3 :
- le constructeur
- le destructeur
- opérator=.


Il les possède aussi. Seulement, elles sont cachées par celles
généraient par le compilateur.


Ma réponse précédente montre simplement de manière succinte que
operator= est traité spécialement (le ctor / dtor / op= sont
qualifiées de "fonctions membres spéciales"), réponse qui contient
d'ailleurs quelques imprécisions :
1 "possède" ici est très immatériel et ne signifie pas grand chose
2 "possède toutes les fonctions membres ..." : sans tenir compte de
l'acces specifier (private public protected)


Cela dit, sémantiquement, les fonctions membres de la classe de base
étant cachées, sont-elles considérées comme des fonctions membres
de la classe dérivée ?

Le standard ne dit pas grand chose à ce sujet :
A member name f in one subobject B hides a member name f in a
sub-object A if A is a base class sub-object of B. Any declarations
that are so hidden are eliminated from consideration.

La dernière phrase tenderait à prouver que de telles fonctions
cachées ne sont pas considérées comme des fonctions membres de la
classe dérivée dans le processus de "member function lookup".

Mais cela reste très subjectif.



Avatar
James Kanze
wrote:
On 23 nov, 22:16, "James Kanze" wrote:
wrote:
On 23 nov, 08:52, "Marc G" wrote:
Mon compilateur m'oblige à redéfinir l'opérateur virtuel = dans les classes
dérivées. Est-ce normal ?
Par contre, pour les autres opérateurs virtuels, ça passe trè s bien (ex :
operator +=())
L'imbrication de mes classes est un peu compliquée, mais j'ai
schématiquement :
Dans la classe de base
virtual CVariant& operator=(CVariant const& right) { /* */ retur n *this; }
Dans certaines classes dérivées, cette définition me va trè s bien, mais le
compilateur me dit que operator= n'est pas définit.
Un copier/coller de la définition de la classe de base et ça ma rche.
Mais pour operator+=, qui modifie lui aussi l'objet, ça marche. Et là je
vois pas pourquoi dans un cas et pas dans l'autre ...
Il y a une "règle" là dessus ?
Oui. Une classe dérivée possède toutes les fonctions membres de sa

classe de base excepté 3 :
- le constructeur
- le destructeur
- opérator=.


Il les possède aussi. Seulement, elles sont cachées par celles
généraient par le compilateur.


Ma réponse précédente montre simplement de manière succinte que
operator= est traité spécialement (le ctor / dtor / op= sont
qualifiées de "fonctions membres spéciales"), réponse qui contient
d'ailleurs quelques imprécisions :
1 "possède" ici est très immatériel et ne signifie pas grand chose
2 "possède toutes les fonctions membres ..." : sans tenir compte de
l'acces specifier (private public protected)

Cela dit, sémantiquement, les fonctions membres de la classe de base
étant cachées, sont-elles considérées comme des fonctions membres
de la classe dérivée ?


Ça dépend de ce qu'on lit. Disons qu'à cet égard, operator= se
comporte comme n'importe quelle autre fonction. La seule vraie
différence, c'est qu'elle *est* déclarée dans la classe dérivée.
Tandis que les autres peuvent ne pas l'être.

Le standard ne dit pas grand chose à ce sujet :
A member name f in one subobject B hides a member name f in a
sub-object A if A is a base class sub-object of B. Any declarations
that are so hidden are eliminated from consideration.

La dernière phrase tenderait à prouver que de telles fonctions
cachées ne sont pas considérées comme des fonctions membres de la
classe dérivée dans le processus de "member function lookup".

Mais cela reste très subjectif.


Tout à fait. Selon les textes, on lit que les éléments d'une
classe de base sont aussi des éléments de la classe dérivée, ou
non. Ce qui est clair, ce sont les règles de recherche d'un nom,
et le fait que dès qu'on trouve un nom dans une portée, on ne
prend pas en compte des portées qui l'englobent (aux exceptions
près dues à l'ADL).

Ce que je voulais dire, c'est que operator= se comporte
exactement comme une autre fonction à cet égard :

class Base
{
public:
void f( Base const& ) ;
} ;

class Derived : public Base
{
public:
void f( Derived const& ) ;
} ;

D unD ;
unD.f( B() ) ; // illégal, on ne trouve pas
// B::f( Base const& )

Dans le cas de l'operator=, ça donne :

class Base
{
public:
Base& operator=( Base const& ) ;
} ;

class Derived : public Base
{
public:
Derived& operator=( Derived const& ) ;
} ;

D unD ;
unD = B() ; // illégal, on ne trouve pas
// B::operator=( Base const& )

La seule différence, c'est qu'on ne peut pas écrire une classe
dérivée sans l'operator= qui cache celui de la base. Selon la
signification qu'on veut donner à posséder, Derived contient un
f(Base const&) ou un operator=(Base const&) qui est invisible,
ou elle n'en possède pas. Mais c'est une propriété générale des
fonctions membres, et il n'y a pas de règles spéciales en ce
concerne l'operator=.

--
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
Marc G
merci à tout les deux qui répondez à ma question. Je comprends mieux le
compilo maintenant...
Avatar
Marc G
Mais je serais intéressé à voir du code qui présente le problème
dont tu parles. De même que je serais intéressé à voir un cas
où un opérator d'affectation virtuel n'est pas une erreur de
conception.


Je n'ai pas fait d'études informatiques et suis donc entièrement autodidacte
dans cette matière. Je veux dire par là que c'est très possible que je
commette parfois (j'espère pas trop quand même) de grosses erreurs... Mais
bon, si je fais un programme qui marche bien et qui me statisfait
intellectuellement, et même si je me trompe conceptuellement sans le savoir,
ça me fait plaisir. Mais je préfère quand même ne pas me tromper :-)

Mon problème en général
Je développe un interpréteur pour un langage que j'ai défini moi-même et
destiné à des traitements statistiques. Dans ce langage, il y a des "objets"
dont je voudrais accéder simplement aux propriétés (j'avais posté à ce sujet
il y a environ 2 semaines).
ex :
table a("une_ table");
propriétés :
a.name
a.size
a.label
a.contents
etc...

je cherche à me simplifier la tâche au niveau de la programmation de
l'interpréteur (et si possible en "externalisant" la plupart des messages
d'erreur dans les objets eux-mêmes).
Au départ je me suis dit que le plus pratique pour accéder aux propriétés
était de retourner des CVariant (je pouvais pas changer le type de retour) à
partir d'une méthode get_property(std::string const&) implémentée dans les
objets eux-même.
Comme cette méthode retourne des objets CVariant, je me suis finalement dit
que ce serais encore mieux si l'interpréteur ne manipulait QUE (ou presque)
des objets CVariant. Evidemment, dans un langage normal, ce serait
drammatique au niveau de la performance. Mais c'est pas le cas ici, parce
que le temps d'interprétation est le plus souvent "infinitésimal" par
rapport au temps d'exécution !
Je veux dire que le langage manipule en général de "gros" objets et que les
données des champs de la table par exemple sont bien sûr des types
primaires.

En gros, j'ai la hiérarchie de classes suivante :

class CVariant {
template<typename T> inline explicit CVariant(T const&);
etc...
// les opérateurs (virtuels ou non)
CVariant *_variant;
};

template<typename T>
class CVariantSpecifique : public CVariant
{
// CVariantSpecifique à partir d'un type quelconque
explicit CVariantSpecifique(T const& t)
: CVariant(Empty()), // _variant=NULL
_t(new T(t))
{}

T* _t;
};

à ma connaissance, c'est l'implémentation "classique" des variants...
Note que dans CVariantSpecifique, je redéfinis l'opérateur virtuel
d'affectation
Ensuite, ce que j'ai fait de plus particulier (enfin je l'ai trouvé tout
seul :-)), c'est de dériver d'autres classes de CVariant pour implémenter
les contantes, les variables, les propriétés, etc... qui seront manipulés
par l'interpréteur uniquement comme des CVariant
ex :
les constantes (nommées ou non)
// ici _reference est le nom de la constante si elle est nommée ou la valeur
exactement telle que saisie par l'utilisateur sinon
template<typename T>
class CConstante : public CVariant
{
typedef T Type;

public :

CConstante(Type const& t,std::string reference)
: CVariant(Empty()),
_reference(reference)
{ _variant=new CVariantSpecifique< T >(t); }

// les opérateurs sont redéfinis pour retourner un message d'erreur
// la constante ne peut être une lvalue
virtual CVariant operator++(int)
{ throw CException(_reference+CCONSTANTE_NOLEFT_MSG);}
etc...
// les constantes ne possèdent pas de propriétés
virtual CVariant& get_property(std::string const& property_name)
{ throw
CException(_reference+std::string(CCONSTANTE_NOPROPERTY_MSG)); }

std::string _reference;
};
typedef CConstante<int> ConstInt;
typedef CConstante<double> ConstDouble;
typedef CConstante<bool> ConstBool;
typedef CConstante<std::string> ConstString;

puis pour les variables de premier niveau (variables pour lesquelles la
propriété parent n'est pas définie)

template<typename T>
class CVariable : public CVariant
{
public :
CVariable(T const& t,std::string const& name,unsigned int const&
statut)
: CVariant(Empty()),
_name(ConstString(name,"name")),
_statut(ConstUInt(statut,"statut")),
_missing(ConstBool(t.get_missing(),"missing"))
{ _variant=new CVariantSpecifique< T >(t); }

virtual CVariant& get_property(std::string const& property_name)
{
if (property_name=="name") return _name;
else if (property_name=="statut") return _statut;
else if (property_name=="missing")
{
// on met à jour la donnée membre _missing pour refléter
l'état de la variable
// todo
return _missing;
}
else return CVariant::_variant->get_property(property_name);
}

ConstString _name;
ConstUInt _statut;
ConstBool _missing;

};

puis je peux définir des propriétés qui possèdent une donnée membre _parent,
_setter et _getter

class CProperty : public CVariant
{


};

voilà, j'en suis à ce niveau pour l'instant...et peut-être que je fais
fausse route !
mon problème était que dans la classe CVariable, le compilateur voulait
impérativement disposer d'une définition de l'opérateur =.
j'ai simplement recopié la définition implémentée dans CVariant
virtual CVariant& operator=(CVariant const& right)
{
if (&right!=this)
{
delete _variant;
_variant=right.duplicate();
}
return *this;
}
et ça marche
Mais je comprends pourquoi maintenant.
Je serais intéressé de savoir ce que tu penses de ce modèle...(voir à
t'envoyer le code si tu es intéressé mais là il faut quand même avoir un peu
de temps pour "l'étudier")
Enfin merci encore de tes réponses
Marc

Avatar
James Kanze
Marc G wrote:
Mais je serais intéressé à voir du code qui présente le probl ème
dont tu parles. De même que je serais intéressé à voir un cas
où un opérator d'affectation virtuel n'est pas une erreur de
conception.


Je n'ai pas fait d'études informatiques et suis donc entièrement auto didacte
dans cette matière.


Moi aussi. Je n'ai pas le bac, et je n'ai jamais fait le moindre
cours d'informatique dans ma vie.

Je veux dire par là que c'est très possible que je
commette parfois (j'espère pas trop quand même) de grosses erreurs...


T'en fais pas. J'en connais des diplômés qui en font d'encore
plus grosses.

Mais
bon, si je fais un programme qui marche bien et qui me statisfait
intellectuellement, et même si je me trompe conceptuellement sans le sa voir,
ça me fait plaisir. Mais je préfère quand même ne pas me tromper :-)

Mon problème en général
Je développe un interpréteur pour un langage que j'ai défini moi-m ême et
destiné à des traitements statistiques. Dans ce langage, il y a des " objets"
dont je voudrais accéder simplement aux propriétés (j'avais posté à ce sujet
il y a environ 2 semaines).
ex :
table a("une_ table");
propriétés :
a.name
a.size
a.label
a.contents
etc...


Une première question : quel est le rÔle de ces objets dans ton
programme ? S'ils représentent des variables, du point de vue de
l'utilisateur, c'est probable qu'ils ne doivent pas supporter
l'affectation, ni la copie. Ce sont ce qu'on appelle souvent les
objets d'entité, ou des objets tout court, au sens d'objet dans
l'orientation objet. Ils ont une identité. Si en revanche ils
représentent des valeurs (d'une variable, mais aussi de
n'importe quelle expression), la copie et l'affectation
s'imposent.

(En passant, les valeurs dans un langage interprété, c'est
l'exemple type du cas -- assez rare en général -- où des types
doivent supporter à la fois le polymorphisme et l'affectation. À
cet égard, je te conseille « Advanced C++ Programming Styles
and Idioms », de James Coplien. Surtout le chapitre 9,
« Emulating Symbolic Language Styles in C++ » et l'idiome
lettre/enveloppe.)

je cherche à me simplifier la tâche au niveau de la programmation de
l'interpréteur (et si possible en "externalisant" la plupart des messag es
d'erreur dans les objets eux-mêmes).
Au départ je me suis dit que le plus pratique pour accéder aux propri étés
était de retourner des CVariant (je pouvais pas changer le type de reto ur) à
partir d'une méthode get_property(std::string const&) implémentée d ans les
objets eux-même.
Comme cette méthode retourne des objets CVariant, je me suis finalement dit
que ce serais encore mieux si l'interpréteur ne manipulait QUE (ou pres que)
des objets CVariant. Evidemment, dans un langage normal, ce serait
drammatique au niveau de la performance. Mais c'est pas le cas ici, parce
que le temps d'interprétation est le plus souvent "infinitésimal" par
rapport au temps d'exécution !
Je veux dire que le langage manipule en général de "gros" objets et q ue les
données des champs de la table par exemple sont bien sûr des types
primaires.

En gros, j'ai la hiérarchie de classes suivante :

class CVariant {
template<typename T> inline explicit CVariant(T const&);
etc...
// les opérateurs (virtuels ou non)
CVariant *_variant;
};

template<typename T>
class CVariantSpecifique : public CVariant
{
// CVariantSpecifique à partir d'un type quelconque
explicit CVariantSpecifique(T const& t)
: CVariant(Empty()), // _variant=NULL
_t(new T(t))
{}

T* _t;
};

à ma connaissance, c'est l'implémentation "classique" des variants...


Prèsque. C'est l'idiome lettre/enveloppe dont j'ai parlé plus
haut, ou prèsque -- en général, le VariantSpecific contient le
type réel par valeur, et non par pointeur et allocation
dynamique.

Note que dans CVariantSpecifique, je redéfinis l'opérateur virtuel
d'affectation =


Pourquoi ? Dans l'idiome classique, on ne manipule que des
Variant de base, et ce n'est qu'elle qui supporte l'affectation.
En revanche, il faut une fonction virtuelle clone, qui
s'implémente dans toutes les classes dérivées. Quelque chose du
genre :

class Variant
{
public:
template< typename T >
explicit Variant( T const& initialValue ) ;

Variant( Variant const& other ) ;
virtual ~Variant() ;
Variant& operator=( Variant const& other ) ;
void swap( Variant& other ) ;
// ... l'interface qu'on veut...
// la fonction toto, par exemple :
void toto() ;

protected:
Variant() ;

private:
virtual Variant* clone() const ;

private:
Variant* myImpl ;
} ;

template< typename T >
class ConcreteVariant
: public Variant
, private boost::uncopiable
{
public:
explicit ConcreteVariant( T const& initialValue ) ;
void toto() ;

private:
virtual Variant* clone() const ;

private:
T myValue ;
} ;

template< typename T >
Variant::Variant(
T const& initialValue )
: myImpl( new ConcreteVariant< T >( initialValue ) )
{
}

template< typename T >
ConcreteVariant< T >::ConcreteVariant(
T const& initialValue )
: myValue( initialValue )
{
}

template< typename T >
void
ConcreteVariant< T >::toto()
{
// Ce qu'on veut...
}

template< typename T >
ConcreteVariant< T >::ConcreteVariant(
ConcreteVariant const&
other )
: myValue( other.myValue )
{
}

template< typename >
Variant*
ConcreteVariant< T >::clone() const
{
return new ConcreteVariant( myValue ) ;
}

Variant::Variant(
Variant const& other )
: myImpl( (assert( other.myImpl != NULL ),
other.myImpl->clone()) )
{
}

Variant::~Variant()
{
delete myImpl ;
}

Variant&
Variant::operator=( Variant const& other )
{
assert( myImpl != NULL ) ;
assert( other.myImpl != NULL ) ;
Variant tmp( other ) ;
swap( tmp ) ;
return *this ;
}

void
Variant::swap(
Variant& other )
{
assert( myImpl != NULL ) ;
assert( other.myImpl != NULL ) ;
std::swap( myImpl, other.myImpl ) ;
}

Variant::Variant()
: myValue( NULL )
{
}

void
Variant::toto()
{
assert( myImpl != NULL ) ;
myImpl->toto() ;
}

// Cette fonction ne doit jamais être appelée !!
Variant*
Variant::clone() const
{
assert( 0 ) ;
}

On rémarque que la classe Variant joue ici deux rôles
distincts, celui de classe concrète, manipulée comme un objet de
valeur par l'utilisateur, et celui de classe de base, dont les
dérivées sont manipulées par la classe de base. On pourrait en
fait définir deux classes -- ça aurait l'avantage que les
fonctions pourraient être virtuelle pûres dans la classe de
base, mais ça exige aussi de réprendre toute l'interface deux
fois, une fois dans la classe de base, et une fois dans la
classe de valeur manipulée par l'utilisateur. (En gros, plus
l'interface est stable, plus la solution à deux classes
distinctes s'impose ; plus elle est évolutive, plus on préfère
n'avoir qu'une classe qui la définit.)

Il y a une invariante très importante (que j'ai matérialisé au
moyen des assert) : quand la classe sert de classe concrète,
myImpl n'est jamais NULL, mais point à une instance de la classe
qui sert de classe de base, et quand la classe sert de classe de
base, myImpl est toujours NULL.

Et on remarque immédiatement que seulement les instances
concrètes supporte l'affectation. La raison en est simple : le
type réel d'une instance concrète est toujours Variant. On peut
donc l'affecter sans avoir le problème d'un changement de type
potentiel. Parce que le problème de l'affectation avec le
polymorphisme, c'est bien là : si j'ai deux types dérivés
différents, que signifie en affecter l'un à l'autre. Je ne peux
pas changer le type d'un objet après sa création. L'idiome
ci-dessus se comporte comme un objet polymorphique, tout en nous
laissant le manipuler comme un objet de valeur, avec
affectation, etc., parce que le polymorphisme est masqué
derrière un type concret. Et tu remarqueras que l'affectation du
type concret ne se manifest pas par l'affectation du type
polymorphique qu'il cache ; au niveau des types polymorphiques,
on « clone » l'objet, afin d'obtenir un nouvel objet du même
type dynamique.

Une variante, évidemment, c'est de partager l'implémentation
entre plusieurs instances concrètes, et de ne faire la copie
qu'en cas de modification (fonction non-const). Ce qui implique
un compteur de référence quelque part, et une gestion plus
onèneuse, mais si la copie est fréquente et chère, et que la
plupart des accès ne modifie rien, ça peut valoir le coup.

Ensuite, ce que j'ai fait de plus particulier (enfin je l'ai trouvé tout
seul :-)), c'est de dériver d'autres classes de CVariant pour impléme nter
les contantes, les variables, les propriétés, etc... qui seront manip ulés
par l'interpréteur uniquement comme des CVariant


C'est l'idée derrière l'idiome ci-dessus, en effet. Tu l'as
peut-être trouvé tout seul, mais tu n'en es pas le premier.

[...]
voilà, j'en suis à ce niveau pour l'instant...et peut-être que je f ais
fausse route !


Le problème, c'est quand tu as quelque chose déclarée comme
Variant, c'est un Variant. Ce n'est pas une classe dérivée de
Variant. Pour avoir des types dynamiques qui diffèrent des types
statiques, il faut passer par des pointeurs ou des références.
Et du coup, ce qu'on affecte, ce sont des pointeurs, et non
l'objet même, et on ne supporte même pas l'affectation. En
général, c'est bien comme ça, mais utiliser les pointeurs, ça
donne bien une sémantique de référence, et non de valeur, et si
on tient à avoir une sémantique de valeur, il faut masquer ces
pointeurs, en quelque sort, et faire des copies polymorphiques
le cas échéant.

mon problème était que dans la classe CVariable, le compilateur voula it
impérativement disposer d'une définition de l'opérateur =.
j'ai simplement recopié la définition implémentée dans CVariant
virtual CVariant& operator=(CVariant const& right)
{
if (&right!=this)
{
delete _variant;
_variant=right.duplicate();


Et que se passe-t-il s'il y a une exception ici ?

}
return *this;
}
et ça marche


Mais pourquoi le voulait-il ? Est-ce que tu as essayé
d'affecter des Variable ? Dans l'idiome classique, ce qui
s'affecte, ce n'est que des Variant. Je ne dis pas que c'est
impossible, mais si tu veux que Variable puisse servir à la fois
comme une implémentation de Variant ET comme un objet en soi, je
crois que tu fais fausse route. Je verrais bien plus une classe
Variable indépendante de Variant, avec une instance de
ConcreteVariant<Variable> quand tu veux la gerer
polymorphiquement.

Mais je comprends pourquoi maintenant.
Je serais intéressé de savoir ce que tu penses de ce modèle...(voir à
t'envoyer le code si tu es intéressé mais là il faut quand même a voir un peu
de temps pour "l'étudier")


D'après ce que je vois, tu es en train de réinventer l'idiome
lettre/envéloppe. L'avantage de l'idiome connu, c'est que
beaucoup de monde y a déjà jeté un coup d'oeil, et que les
problèmes en ont été déjà résolus.

Sinon, c'est bien comme ça qu'il faut faire. Et si tu as
réelement trouvé ça tout seul, châpeau.

--
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
Marc G
Une première question : quel est le rÔle de ces objets dans ton
programme ?


Je développe un logiciel destiné à effectuer des traitements statistiques
sur des données.
Pour cela, je cherche à implémenter un langage "simple" compréhensible par
quelqu'un qui ne fait pas de blocage sur l'ordinateur mais qui n'est pas
informaticien non plus :-) ! Dans le monde des enquêtes, c'est relativement
courant...
(je sais pas si tu connais le langage SAS de SAS Institute, développé dans
les années 70 et qui n'a pas fondamentalement évolué dans sa structure. Dans
la version 8, il n'est toujours pas possible de définir des fonctions -c'est
quand même un concept bien pratique !- et il faut se débrouiller avec un
langage macro que mon intelligence normale ne me permet pas de comprendre.
En gros, j'écris "presque n'importe quoi" et je regarde ce que ça fait !
C'est souvent lassant...). Tous mes clients qui utilisent SAS seraient prêts
à changer pour n'importe quoi de plus simple, surtout qu'en général ils
veulent juste faire des choses basiques (genre tris croisés). Cette petite
intro rapide, c'est pour t'expliquer le contexte !
Les utilisateurs ont des tables de données importantes, au minimum 1.000
enregistrements et souvent + de 100.000 et il veulent
"traiter" les données.
Par exemple, dans une enquête ils ont demandé l'âge du chef de ménage et les
revenus du ménage et ils veulent juste croiser des tranches d'âge avec des
tranches de revenus.

tu vas écrire des trucs du genre (dans le style SAS):

tranche_age=6;
if age<60 then tranche_age=5;
if age<50 then tranche_age=4;
if age<40 then tranche_age=3;
if age<30 then tranche_age=2;
if age<20 then tranche_age=1;

Pour moi, la variable age est un "tableau" de 1000 entiers par exemple.
Tu vois qu'il s'agit de gros objets. C'est pourquoi je préfère que le
tableau réel soit alloué dans le tas, d'autant que j'ai prévu des
constructeurs qui récupèrent juste l'adresse de l'objet (en en prenant
possession ou pas).
En fait, j'ai défini une classe particulière
template <typename T> class CValue
pour implémenter les valeurs manquantes pour les types primaires -ou
autres - et c'est donc un "tableau" de 1000 CValue<int>.
Ma classe CValue répond aussi à une autre nécessité : définir les quelques
méthodes qui doivent être implémentées par TOUS les objets susceptibles
d'être mis dans un CVariant. Et une méthode est essentielle, c'est
virtual CVariant& get_property(std::string const& property_name)
Note que get_property retourne bien une référence et non un CVariant.
Ma classe CVariant a pour but de manipuler un ensemble d'objets
hiérarchisés, où les uns sont des propriétés des autres. C'est pas
exactement une classe CVariant "habituelle".

Par exemple, quand l'utilisateur écrit
int x=1;
l'interpréteur va créer un objet
CVariable<int> obj_x("x",1); // nom et valeur dans le constructeur
La valeur 1 va être stockée dans un objet CValue crée dans le tas et pointé
par la donnée membre de CVariantSpecifique.
CVariable est une classe dérivée de CVariant qui redéfini get_property
CVariable représente pour moi les variables "primaires", c'est à dire qui
n'ont pas de parent.
Les objets de cette classe possèdent par exemple les propriétés
name (ici "x")
typename (int/string/...)
missing
statut ->lecture seule O/N (pour les const)

si l'utilisateur tape
x.name
l'interpréteur appelle la méthode obj_x.get_property qui va retourner une
réference vers un CVariant qui contient le nom de la variable (en fait il
s'agira d'une référence vers un objet CConstante<U> dérivé de CVariant et
qui est une donnée membre de CVariable<T>)
Pour comprendre le schéma fonctionnel, il y a une distinction entre la
valeur de l'objet, qui est stockée dans la classe CVariant Specifique et le
type (au sens constante/variable/propriété) de l'objet qui est déterminé par
le type de la classe dérivée de CVariant utilisé lors de la création de
l'objet !
Mais TOUS les objets que je manipule par l'interpréteur (je veux dire
accessibles aux utilisateurs) définissent la méthode get_property (et
quelques autres). C'est donc aussi le cas de la valeur de l'objet, qui peut
d'ailleurs être elle-même un objet et avoir ou non des propriétés.
get_property gère aussi tous les messages d'erreurs liés à l'objet (lecture
seule/propriété inexistante...)

Au sujet de l'opérateur Tu as raison, dans la classe CVariantSpecific, il ne faut pas le redéfinir.
Je l'avais fait mais c'est parfaitement inutile parce que CVariantSpecifique
n'est jamais une lvalue car on ne manipule que des objets qui ne sont pas
directement "parent" (l'inverse de dérivé, je ne sais pas comment on dit) de
CVariantSpecific.
Pour les autres classes dérivées de CVariant par contre, il me faut le
redéfinir.

Pour que tu comprennes mieux, je te livre la définition simple des
constantes.

template<typename T>
class CConstante : public CVariant
{
typedef T Type;

public :

CConstante(Type const& t,std::string reference)
: CVariant(Empty()),
_reference(reference)
{ _variant=new CVariantSpecifique< T >(t); }

CConstante(CConstante const& x)
: CVariant(Empty()),
_reference(x._reference)
{ _variant=x._variant->duplicate();}

virtual ~CConstante() {}

//protected :

// la constante ne peut être une lvalue
virtual CVariant operator++(int)
{ throw CException(_reference+CCONSTANTE_NOLEFT_MSG);}
virtual CVariant operator--(int)
{ throw CException(_reference+CCONSTANTE_NOLEFT_MSG);}
virtual CVariant& operator=(CVariant const&)
{ throw CException(_reference+CCONSTANTE_NOLEFT_MSG);} // <- ici
par exemple
virtual CVariant& operator++()
{ throw CException(_reference+CCONSTANTE_NOLEFT_MSG);}
virtual CVariant& operator--()
{ throw CException(_reference+CCONSTANTE_NOLEFT_MSG);}
virtual CVariant& operator+=(CVariant const&)
{ throw CException(_reference+CCONSTANTE_NOLEFT_MSG);}
virtual CVariant& operator-=(CVariant const&)
{ throw CException(_reference+CCONSTANTE_NOLEFT_MSG);}
virtual CVariant& operator*=(CVariant const&)
{ throw CException(_reference+CCONSTANTE_NOLEFT_MSG);}
virtual CVariant& operator/=(CVariant const&)
{ throw CException(_reference+CCONSTANTE_NOLEFT_MSG);}

virtual void set_missing(void)
{ throw CException(_reference+CCONSTANTE_NOLEFT_MSG);}

virtual CVariant* duplicate(void) const { return new
CConstante(*this); }

std::string _reference;

// les constantes ne possèdent pas de propriétés
virtual CVariant& get_property(std::string const& property_name)
{ throw
CException(_reference+std::string(CCONSTANTE_NOPROPERTY_MSG)); }

virtual bool contains(CVariant const& right) const { return false;}
virtual bool read_only(void) const { return true;}
};

// instanciations explicites pour les types primaires

#define specific_macro(T)

template class CConstante< T >;
global_macro(specific_macro)
#undef specific_macro

// spécialisations pour les types primaires

#define specific_macro(T)

template<> CConstante<T>::CConstante(T const& t,std::string reference)

: CVariant(Empty()),

_reference(reference)

{ _variant=new CVariantSpecifique< CValue< T > >( CValue< T
(t)); }
global_macro(specific_macro)

#undef specific_macro

typedef CConstante<undefined> ConstUndefined;
typedef CConstante<unsigned int> ConstUInt;
typedef CConstante<int> ConstInt;
typedef CConstante<double> ConstDouble;
typedef CConstante<bool> ConstBool;
typedef CConstante<std::string> ConstString;

Pour les CProperty, on a un truc du genre


// -----------------
// | CVariant |
// ----------------- -----------------
// | _variant!= NULL | -----------> | CVariant
|
// ----------------- -----------------
// |
| _variant = NULL |
//
-----------------
// --------------------- | _t!=
NULL |
// | CProperty
-----------------
// ---------------------
// | _parent | <- pointeur vers le
TYPE
// | _name | <- objet
CConstante<std::string>
// | _statut | <- objet
CConstante<unsigned int>
// | _missing | <- objet
CConstante<bool>
// | ........ |
// -----------------

En fait mon interpréteur ne manipule que des objets CVariants.
Le seul moment où je dois manipuler d'autres classes dérivées, c'est au
moment de la déclaration.
S'agit-il d'une constante, variable et bien sûr de quel objet.
ex:
table a;
CVariable< CTable> obj_table("a"); par exemple.
Mais comme tous les opérateurs peuvent être potentiellement utilisés par les
CVariants, les objets disposent d'une données membres static unsigned int
qui mémorise les opérateurs légitimes sur l'objet. Pour les autres, une
exception est lancée.
Seule lourdeur, il me faut définir toutes les conversions 2 à 2.
Mais bon, je suppose qu'il faut le faire de toutes façons, quelle que soit
la méthode choisie. Pour les objets qui peuvent se convertir, il faut
l'écrire :-).
Et pour les autres, c'est vite dit (dans une petite macro par exemple pour
aller plus vite).
Et dans ma classe de Contexte, j'aurai (c'est pas encore programmé) , sauf
surprise :-), que des pointeurs vers des CVariants.
J'espère que j'ai été clair et pas trop long...
Si tu vas jusqu'au bout Et que j'ai été clair, ça m'interesse de savoir si
c'est un modèle sinon connu (on développe pas tous les modèles dans la vie
!), du moins intéressant et pertinant.
Bonne lecture :-)
Marc

Avatar
Sylvain
Marc G wrote on 25/11/2006 21:32:
Une première question : quel est le rÔle de ces objets dans ton
programme ?


Je développe un logiciel destiné à effectuer des traitements statistiques
sur des données.


et SQL n'est pas applicable ?

(éventuellement avec un clicodrome (html, php, perl, whatever) pour
construire "à la souris" les requêtes).

Sylvain.


1 2