OVH Cloud OVH Cloud

Downcasting classe template

5 réponses
Avatar
sej
Bonjour,
J'ai le modèle suivant :


Classe de base : Object


class Object
{

public :
typedef enum {
MEM_USER,
MEM_DSP,
}TYPE_Mem;

typedef enum {
OBJ_IMAGE,
OBJ_VECTOR,
OBJ_SCALAR,
}TYPE_Object;

typedef enum {
DATA_U8,
DATA_S8,
DATA_U16,
DATA_S16,
DATA_U32,
DATA_S32,
DATA_FLOAT,
}TYPE_Data;

Object165(
const TYPE_Mem typeMem,
const TYPE_Object typeObj,
const TYPE_Data typeData);

TYPE_Object GetTypeObject () const {return _typeObj;}
TYPE_Mem GetTypeMem () const {return _typeMem;}
TYPE_Data GetTypeData () const {return _typeData;}

u32* GetMemU32 () { return _mem; }
void SetMemU32 (u32* mem) { _mem = mem; }

// impossible de dériver cette méthode à cause du type de retour !
//virtual u32* GetMem () { return _mem; }

private :
TYPE_Mem _typeMem;
TYPE_Object _typeObj;
TYPE_Data _typeData;

u32 *_mem;
};


template<typename Type>
class ObjectUser : public Object
{
...
private :
Type *_mem;
}

template<typename Type>
class ObjectDSP : public Object;
{
...
private :
Type *_mem;
}



template<typename Type>
class ImageUser : public ObjectUser<Type>;


template<typename Type>
class ImageDSP : public ObjectDSP<Type>;


Donc en résumé, la classe de base est Object qui est dérivée en
ObjectUser et ObjectDSP.
ObjectUser est dérivée en ImageUser et d'autre classes du même genre.
idem pour ObjectDSP


J'ai utilisé des templates car je dois utiliser les types u8, s8, u16,
s16, u32, s32 et float.
Le but est de faire des calculs mathématiques sur des images entre autre.

Dans Object j'ai donc un pointeur sur la zone mémoire (u32 *_mem) mais
ce pointeur est en 32 bits. Or les calculs peuvent être en 8 bits par
exemple.
Je cherche donc une solution pour retrouver soit un pointeur sur la
classe de base, soit un pointeur sur la zone mémoire de base dans le bon
type (<Type*> _mem).

Par exemple, je crée 3 Objects :
const u32 szX = 20, szY = 12;
Object *obA = new ImageUser<u8>(szX, szY);
Object *obB = new ImageUser<u16>(szX, szY);
Object *obR = new ImageUser<s32>(szX, szY);

J'ai aussi créé une classe MathUser dans laquelle j'ai cette fonction :

void
MathUser::Add2Image(Object &obR, Object &obA, Object &obB)
{
// test de compatibilité entre obR, obA, obB : taille ....

// Si OK => calcul
for(u32 i=0; i<obR.GetSize()
{
// c'est là mon problème, je dois retrouver le type de base
// ce code ne peut pas etre compilé évidemment
obR.GetMem()[i] = obA.GetMem()[i] + obB.GetMem[i];
}
}


J'ai mis en place un système d'ID (TYPE_Mem, TYPE_Object, TYPE_Data) qui
me permet de savoir à quel type d'objet j'ai affaire ; c'est comme un
typeid()
Comment est-ce que je peux faire pour récupérer soit les pointeurs sur
ImageUser<u8> ..., soit sur les données elles-mêmes : u8* ...
J'avais pensé à un système de MACRO pour récupérer un pointeur du bon
type (sorte de downcasting) mais ça ne marche pas car j'en ai besoin à
l'éxécution et non à la compilation !
Malheureusement je ne peux pas non plus utiliser une méthode virtuelle
du genre GetMem() qui me retourne un pointeur car j'utilise des
templates et donc il me faut <Type*>GetMem()


Est-ce qu'il y a une solution simple qui me permet de coder ce dont j'ai
besoin sans avoir à écrire toutes les combinaisons possibles et
imaginables ?
(il y a plus de 400 combinaisons rien que pour une addition et je dois
implémenter une bonne cinquantaine d'opérandes !)

5 réponses

Avatar
kanze
sej wrote:

J'ai le modèle suivant :


Je ne suis pas sûr d'avoir réelement compris ton problème, mais
plusieurs choses me viennent à l'ésprit.

Classe de base : Object

class Object
{
public :
typedef enum {
MEM_USER,
MEM_DSP,
}TYPE_Mem;


Pourquoi le typedef, et non pas simplement :

enum TYPE_Mem { ... } ;

(Aussi, je trouve qu'il y a un peu trop de majuscules. J'aurais
fait quelque chose du genre :

enum MemoryType { user, dsp } ;

Et en passant, le virgule après MEM_DSP est formellement
interdit pas le langage, même si certains compilateurs
l'acceptent.)

typedef enum {
OBJ_IMAGE,
OBJ_VECTOR,
OBJ_SCALAR,
}TYPE_Object;


Comme ci-dessus.

typedef enum {
DATA_U8,
DATA_S8,
DATA_U16,
DATA_S16,
DATA_U32,
DATA_S32,
DATA_FLOAT,
}TYPE_Data;

Object165(
const TYPE_Mem typeMem,
const TYPE_Object typeObj,
const TYPE_Data typeData);

TYPE_Object GetTypeObject () const {return _typeObj;}
TYPE_Mem GetTypeMem () const {return _typeMem;}
TYPE_Data GetTypeData () const {return _typeData;}

u32* GetMemU32 () { return _mem; }
void SetMemU32 (u32* mem) { _mem = mem; }

// impossible de dériver cette méthode à cause du type de retour !
//virtual u32* GetMem () { return _mem; }

private :
TYPE_Mem _typeMem;


L'utilisation de _ comme préfixe peut poser de problèmes. Mieux
vaut comme suffixe (AMHA peu esthétique), ou un préfixe du genre
m_ ou my. Ou trouver de bons noms, pour que le préfixe ne soit
pas nécessaire.

TYPE_Object _typeObj;
TYPE_Data _typeData;

u32 *_mem;
};


Plusieurs points :

-- Si j'ai bien compris, _mem pourrait pointer en fait à
n'importe quel type. Alors, c'est un void* qu'il faut.

-- Tu dérives de cette classe. Avec la possibilité éventuelle
de vouloir supprimer un objet alloué dynamiquement par un
pointeur à la base. Alors, un destructeur virtuel s'impose.

-- Qui est responsable de la mémoire pointé par _mem ? Si tu
n'utilises pas le collecteur de Boehm, il faut bien s'en
occuper.

-- Et quel est la politique vis-à-vis des copies et de
l'affectation ? Est-ce qu'elles sont supportées, et si oui,
avec quelle sémantique, copie profonde, ou une sémantique de
référence ? (Typiquement, quand on a une hièrarchie
polymorphique, on interdit l'affectation, et souvent la
copie, ou au moins, on force à ce qu'elle passe par une
fonction virtuelle, du genre clone().)

-- En fait, quel est le but de l'héritage ici, dans la mesure
où il n'y a pas de fonctions virtuelles ? Voire même, quel
est le but de la classe ? Quel est son rôle dans
l'application ? Que sont ses responsibilités ?

template<typename Type>
class ObjectUser : public Object
{
...
private :
Type *_mem;
}

template<typename Type>
class ObjectDSP : public Object;
{
...
private :
Type *_mem;
}

template<typename Type>
class ImageUser : public ObjectUser<Type>;

template<typename Type>
class ImageDSP : public ObjectDSP<Type>;

Donc en résumé, la classe de base est Object qui est dérivée
en ObjectUser et ObjectDSP. ObjectUser est dérivée en
ImageUser et d'autre classes du même genre. idem pour
ObjectDSP

J'ai utilisé des templates car je dois utiliser les types u8,
s8, u16, s16, u32, s32 et float.
Le but est de faire des calculs mathématiques sur des images
entre autre.


C'est peut-être le but du programme, mais quel rapport aux
classes présentées ici ? Elles ne font pas de calculs.

Dans Object j'ai donc un pointeur sur la zone mémoire (u32
*_mem) mais ce pointeur est en 32 bits.


Le pointeur est en 32 bits, ou il pointe à un bloc de mémoire de
32 bits.

Or les calculs peuvent être en 8 bits par exemple.

Je cherche donc une solution pour retrouver soit un pointeur
sur la classe de base,


Si tu as un pointeur à la classe dérivée, il se convertit
implicitement en pointeur à la classe de base.

soit un pointeur sur la zone mémoire de
base dans le bon type (<Type*> _mem).


N'importe quel T* peut se convertir en void*, et vice versa. La
convertion vers void* est même implicite ; pour faire celle de
void*, on se sert de static_cast. En principe, la conversion de
void* vers %* n'est valide que ce le void* result d'une
conversion du T*, mais dans la pratique, dans la mesure où il
s'agit des types fondamentaux, si le mémoire est correctement
alignée, ça doit aller.

N'empèche que je ne vois pas le problème que tu essaies à
résoudre.

Par exemple, je crée 3 Objects :
const u32 szX = 20, szY = 12;
Object *obA = new ImageUser<u8>(szX, szY);
Object *obB = new ImageUser<u16>(szX, szY);
Object *obR = new ImageUser<s32>(szX, szY);

J'ai aussi créé une classe MathUser dans laquelle j'ai cette fonction :

void
MathUser::Add2Image(Object &obR, Object &obA, Object &obB)
{
// test de compatibilité entre obR, obA, obB : taille ....

// Si OK => calcul
for(u32 i=0; i<obR.GetSize()
{
// c'est là mon problème, je dois retrouver le type de base
// ce code ne peut pas etre compilé évidemment
obR.GetMem()[i] = obA.GetMem()[i] + obB.GetMem[i];
}
}


Le problème ici, c'est que Add2Image doit en fait être
polymorphique. (Ou un template, si on veut le typage statique.
C'est un cas où les templates sont bien plus simples à utiliser
que l'héritage.)

Enfin, je verrais quelque chose comme :

template< typename T >
class TypedObject : public Object ...
// Classe « typée » intermédiaire entre Object et
// UserObject ou DSPObject...

template< typename T >
void
TypedObject< T >::addToMe( Object const& other )
{
TypedObject const& rhs
= dynamic_cast< TypedObject const& >( other ) ;
if ( size() != other.size() ) {
throw bad_size() ;
}
for ( int i = 0 ; i < size() ; ++ i ) {
mem()[ i ] += rhs.mem()[ i ] ;
}
}

où addToMe est une fonction virtuelle pûre dans Object. (J'ai
un peu l'impression que l'utilisation même d'une classe de base
non-typée n'est pas une bonne idée, mais je ne connais pas assez
de l'application pour en être sûr.)

J'ai mis en place un système d'ID (TYPE_Mem, TYPE_Object,
TYPE_Data) qui me permet de savoir à quel type d'objet j'ai
affaire ; c'est comme un typeid()


Et pourquoi pas se servir de typeid() ? Ou dynamic_cast ?

Comment est-ce que je peux faire pour récupérer soit les
pointeurs sur ImageUser<u8> ...,


À partir d'Object* ? Avec dynamic_cast, évidemment.

soit sur les données elles-mêmes : u8* ...


Une fois que tu as un pointeur sur la classe dérivée, la reste
doit couler de source. Mais en général, c'est mieux d'éviter le
besoin d'un pointeur sur la classe dérivée, avec des fonctions
virtuelles qui conviennent.

J'avais pensé à un système de MACRO pour récupérer un pointeur
du bon type (sorte de downcasting) mais ça ne marche pas car
j'en ai besoin à l'éxécution et non à la compilation !


C'est pour ça qu'on a des fonctions virtuelles.

Malheureusement je ne peux pas non plus utiliser une méthode
virtuelle du genre GetMem() qui me retourne un pointeur car
j'utilise des templates et donc il me faut <Type*>GetMem()


Le problème, c'est qu'il faudrait de toute façon une
implémentation spécifique de ton algorithme pour chaque type de
pointeur. La solution, SI on a besoin d'une résolution
dynamique, c'est une fonction virtuelle.

Note bien qu'au moins d'avoir absolument besoin d'une résolution
dynamique, la solution template est nettement supérieur ici. Tu
as en fait une précondition sur les types : dans le cas
d'addition, par exemple, que les deux tableaux soient de même
type. Et les templates sont conçus précisement pour exprimer des
prédicats sur les types.

Est-ce qu'il y a une solution simple qui me permet de coder ce
dont j'ai besoin sans avoir à écrire toutes les combinaisons
possibles et imaginables ?


Avec une aiguillage dynamique, ça va être difficile. Peut-être
quelque chose du genre :

class AbstractOperator
{
public:
virtual ~AbstractOperator() {}
virtual void add2Image(
Object& dest,
Object const& op1,
Object const& op2 ) const = 0 ;
// ...
} ;

template< typename Result, typename Op1, typename Op2 >
class Operator : public AbstractOperator
{
public:
virtual void add2Image(
Object& dest,
Object const& op1,
Object const& op2 ) const
{
Result& d = dynamic_cast< Result >( dest) ;
Op1 const& o1 = dynamic_cast< Op1 >( op1 ) ;
Op2 const& o2 = dynamic_cast< Op2 >( op2 ) ;
// boucle avec calcul...
}
} ;

Ensuite, tu crées une instance (statique, probablement) de
chacun des types dérivés, et un std::map< TypeTriplet,
AbstractOperator const* >. Finalement, la fonction « générique »
créer un TypeTriplet à partir des types dynamiques de ses
paramètres, et cherche la classe opératrice voulue dans le map.

(il y a plus de 400 combinaisons rien que pour une addition et
je dois implémenter une bonne cinquantaine d'opérandes !)


Un mélange de l'héritage, des templates, et une simulation de
l'aiguillage multiple pourraient le faire. N'empèche que
j'essaierais de rédéfinir le problème pour qu'il ne soit pas
nécessaire.

--
James Kanze GABI Software
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
sej
sej wrote:

J'ai le modèle suivant :


Je ne suis pas sûr d'avoir réelement compris ton problème, mais
plusieurs choses me viennent à l'ésprit.

Classe de base : Object

class Object
{
public :
typedef enum {
MEM_USER,
MEM_DSP,
}TYPE_Mem;


Pourquoi le typedef, et non pas simplement :

enum TYPE_Mem { ... } ;

(Aussi, je trouve qu'il y a un peu trop de majuscules. J'aurais
fait quelque chose du genre :

enum MemoryType { user, dsp } ;

Et en passant, le virgule après MEM_DSP est formellement
interdit pas le langage, même si certains compilateurs
l'acceptent.)

typedef enum {
OBJ_IMAGE,
OBJ_VECTOR,
OBJ_SCALAR,
}TYPE_Object;


Comme ci-dessus.

typedef enum {
DATA_U8,
DATA_S8,
DATA_U16,
DATA_S16,
DATA_U32,
DATA_S32,
DATA_FLOAT,
}TYPE_Data;

Object165(
const TYPE_Mem typeMem,
const TYPE_Object typeObj,
const TYPE_Data typeData);

TYPE_Object GetTypeObject () const {return _typeObj;}
TYPE_Mem GetTypeMem () const {return _typeMem;}
TYPE_Data GetTypeData () const {return _typeData;}

u32* GetMemU32 () { return _mem; }
void SetMemU32 (u32* mem) { _mem = mem; }

// impossible de dériver cette méthode à cause du type de retour !
//virtual u32* GetMem () { return _mem; }

private :
TYPE_Mem _typeMem;


L'utilisation de _ comme préfixe peut poser de problèmes. Mieux
vaut comme suffixe (AMHA peu esthétique), ou un préfixe du genre
m_ ou my. Ou trouver de bons noms, pour que le préfixe ne soit
pas nécessaire.

TYPE_Object _typeObj;
TYPE_Data _typeData;

u32 *_mem;
};


Plusieurs points :

-- Si j'ai bien compris, _mem pourrait pointer en fait à
n'importe quel type. Alors, c'est un void* qu'il faut.

-- Tu dérives de cette classe. Avec la possibilité éventuelle
de vouloir supprimer un objet alloué dynamiquement par un
pointeur à la base. Alors, un destructeur virtuel s'impose.

-- Qui est responsable de la mémoire pointé par _mem ? Si tu
n'utilises pas le collecteur de Boehm, il faut bien s'en
occuper.


Il y a une classe qui gère les allocations et désallocations mémoires.

-- Et quel est la politique vis-à-vis des copies et de
l'affectation ? Est-ce qu'elles sont supportées, et si oui,
avec quelle sémantique, copie profonde, ou une sémantique de
référence ? (Typiquement, quand on a une hièrarchie
polymorphique, on interdit l'affectation, et souvent la
copie, ou au moins, on force à ce qu'elle passe par une
fonction virtuelle, du genre clone().)


Les copies sont interdites et l'affectation est faite par un memcopy de
la mémoire après test de compatibilité.

-- En fait, quel est le but de l'héritage ici, dans la mesure
où il n'y a pas de fonctions virtuelles ? Voire même, quel
est le but de la classe ? Quel est son rôle dans
l'application ? Que sont ses responsibilités ?


Le but de l'héritage est de dissocier les objects User des objects DSP.
En fait les objets User sont stockée en RAM du PC et les DSP dans du
hard ! Mais je ne peux pas faire de GetMem virtuel à cause du type de
retour.

template<typename Type>
class ObjectUser : public Object
{
...
private :
Type *_mem;
}

template<typename Type>
class ObjectDSP : public Object;
{
...
private :
Type *_mem;
}

template<typename Type>
class ImageUser : public ObjectUser<Type>;

template<typename Type>
class ImageDSP : public ObjectDSP<Type>;

Donc en résumé, la classe de base est Object qui est dérivée
en ObjectUser et ObjectDSP. ObjectUser est dérivée en
ImageUser et d'autre classes du même genre. idem pour
ObjectDSP

J'ai utilisé des templates car je dois utiliser les types u8,
s8, u16, s16, u32, s32 et float.
Le but est de faire des calculs mathématiques sur des images
entre autre.


C'est peut-être le but du programme, mais quel rapport aux
classes présentées ici ? Elles ne font pas de calculs.


C'est normal que les classes ne font pas de calcul car il y a trop de
cas à gérer directement : il y a 3 types d'Objects (User, PC et DSP), 7
types de donnée (u8, s8, u16, s16, u32, s32 et float), environ 5 Objects
différents (Image, scalaire, vecteur ...)
Donc pour écrire toutes les additions entre les 3 types d'objets ça en
devient impossible.
Donc la solution était de faire une classe math qui s'en charge en
'bricolant' sur le type des données.

Dans Object j'ai donc un pointeur sur la zone mémoire (u32
*_mem) mais ce pointeur est en 32 bits.


Le pointeur est en 32 bits, ou il pointe à un bloc de mémoire de
32 bits.

Or les calculs peuvent être en 8 bits par exemple.

Je cherche donc une solution pour retrouver soit un pointeur
sur la classe de base,


Si tu as un pointeur à la classe dérivée, il se convertit
implicitement en pointeur à la classe de base.

soit un pointeur sur la zone mémoire de
base dans le bon type (<Type*> _mem).


N'importe quel T* peut se convertir en void*, et vice versa. La
convertion vers void* est même implicite ; pour faire celle de
void*, on se sert de static_cast. En principe, la conversion de
void* vers %* n'est valide que ce le void* result d'une
conversion du T*, mais dans la pratique, dans la mesure où il
s'agit des types fondamentaux, si le mémoire est correctement
alignée, ça doit aller.

N'empèche que je ne vois pas le problème que tu essaies à
résoudre.
Par exemple, je crée 3 Objects :
const u32 szX = 20, szY = 12;
Object *obA = new ImageUser<u8>(szX, szY);
Object *obB = new ImageUser<u16>(szX, szY);
Object *obR = new ImageUser<s32>(szX, szY);

J'ai aussi créé une classe MathUser dans laquelle j'ai cette fonction :

void
MathUser::Add2Image(Object &obR, Object &obA, Object &obB)
{
// test de compatibilité entre obR, obA, obB : taille ....

// Si OK => calcul
for(u32 i=0; i<obR.GetSize()
{
// c'est là mon problème, je dois retrouver le type de base
// ce code ne peut pas etre compilé évidemment
obR.GetMem()[i] = obA.GetMem()[i] + obB.GetMem[i];
}
}


Le problème ici, c'est que Add2Image doit en fait être
polymorphique. (Ou un template, si on veut le typage statique.
C'est un cas où les templates sont bien plus simples à utiliser
que l'héritage.)

Enfin, je verrais quelque chose comme :

template< typename T >
class TypedObject : public Object ...
// Classe « typée » intermédiaire entre Object et
// UserObject ou DSPObject...

template< typename T >
void
TypedObject< T >::addToMe( Object const& other )
{
TypedObject const& rhs
= dynamic_cast< TypedObject const& >( other ) ;
if ( size() != other.size() ) {
throw bad_size() ;
}
for ( int i = 0 ; i < size() ; ++ i ) {
mem()[ i ] += rhs.mem()[ i ] ;
}
}

où addToMe est une fonction virtuelle pûre dans Object. (J'ai
un peu l'impression que l'utilisation même d'une classe de base
non-typée n'est pas une bonne idée, mais je ne connais pas assez
de l'application pour en être sûr.)

J'ai mis en place un système d'ID (TYPE_Mem, TYPE_Object,
TYPE_Data) qui me permet de savoir à quel type d'objet j'ai
affaire ; c'est comme un typeid()


Et pourquoi pas se servir de typeid() ? Ou dynamic_cast ?

Comment est-ce que je peux faire pour récupérer soit les
pointeurs sur ImageUser<u8> ...,


À partir d'Object* ? Avec dynamic_cast, évidemment.

soit sur les données elles-mêmes : u8* ...


Une fois que tu as un pointeur sur la classe dérivée, la reste
doit couler de source. Mais en général, c'est mieux d'éviter le
besoin d'un pointeur sur la classe dérivée, avec des fonctions
virtuelles qui conviennent.

J'avais pensé à un système de MACRO pour récupérer un pointeur
du bon type (sorte de downcasting) mais ça ne marche pas car
j'en ai besoin à l'éxécution et non à la compilation !


C'est pour ça qu'on a des fonctions virtuelles.

Malheureusement je ne peux pas non plus utiliser une méthode
virtuelle du genre GetMem() qui me retourne un pointeur car
j'utilise des templates et donc il me faut <Type*>GetMem()


Le problème, c'est qu'il faudrait de toute façon une
implémentation spécifique de ton algorithme pour chaque type de
pointeur. La solution, SI on a besoin d'une résolution
dynamique, c'est une fonction virtuelle.

Note bien qu'au moins d'avoir absolument besoin d'une résolution
dynamique, la solution template est nettement supérieur ici. Tu
as en fait une précondition sur les types : dans le cas
d'addition, par exemple, que les deux tableaux soient de même
type. Et les templates sont conçus précisement pour exprimer des
prédicats sur les types.

Est-ce qu'il y a une solution simple qui me permet de coder ce
dont j'ai besoin sans avoir à écrire toutes les combinaisons
possibles et imaginables ?


Avec une aiguillage dynamique, ça va être difficile. Peut-être
quelque chose du genre :

class AbstractOperator
{
public:
virtual ~AbstractOperator() {}
virtual void add2Image(
Object& dest,
Object const& op1,
Object const& op2 ) const = 0 ;
// ...
} ;

template< typename Result, typename Op1, typename Op2 >
class Operator : public AbstractOperator
{
public:
virtual void add2Image(
Object& dest,
Object const& op1,
Object const& op2 ) const
{
Result& d = dynamic_cast< Result >( dest) ;
Op1 const& o1 = dynamic_cast< Op1 >( op1 ) ;
Op2 const& o2 = dynamic_cast< Op2 >( op2 ) ;
// boucle avec calcul...
}
} ;

Ensuite, tu crées une instance (statique, probablement) de
chacun des types dérivés, et un std::map< TypeTriplet,
AbstractOperator const* >. Finalement, la fonction « générique »
créer un TypeTriplet à partir des types dynamiques de ses
paramètres, et cherche la classe opératrice voulue dans le map.

(il y a plus de 400 combinaisons rien que pour une addition et
je dois implémenter une bonne cinquantaine d'opérandes !)


Un mélange de l'héritage, des templates, et une simulation de
l'aiguillage multiple pourraient le faire. N'empèche que
j'essaierais de rédéfinir le problème pour qu'il ne soit pas
nécessaire.

--
James Kanze GABI Software
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




Après réflexion, je vais passer la classe Object en template afin
d'avoir un pointeur sur le bon type de donnée et éviter le void* (pour
_mem).
En fait tout le problème était de stocker les Objects dans des noeud :

class Node
{
public :
Node(Object *obA, Object *obB, const Operand op);
Node(Node *nodeA, Node *nodeB, const Operand op);
Node(Object *obA, Node *nodeB, const Operand op);
Node(Node *nodeA, Object *obB, const Operand op);
Node(Object *obA, const Operand op);
Node(Node *nodeA, const Operand op);

Object* GetObjectA () {return _obA; }
Object* GetObjectB () {return _obB; }
Node* GetNodeA () {return _nodeA; }
Node* GetNodeB () {return _nodeB; }

private :
Node(const Node&); // not allowed
Node& operator=(const Node&); // not allowed

Object *_obA;
Object *_obB;
Node *_nodeA;
Node *_nodeB;
const Operand _op;
};


Donc si les Objects sont templates, les noeuds aussi et là ça coince,
car il n'est pas possible de stocker un Node<Object<u8>, Object<u16>>
dans un Node<Node, Object<u8>> par exemple !

Pour conclure, j'enlève les noeuds et donc je peux mettre mes Objects en
template et faire une classe Math template dans laquelle je teste avec
des dynamic_cast que les Objects sont compatibles.
Du coup le modèle marche beaucoup mieux.

Merci pour toutes ces informations utiles.
@ bientot
sej


Avatar
kanze
sej wrote:
sej wrote:

-- Qui est responsable de la mémoire pointé par _mem ? Si tu
n'utilises pas le collecteur de Boehm, il faut bien s'en
occuper.


Il y a une classe qui gère les allocations et désallocations mémoir es.


Est-ce que ça ne serait pas mieux qu'elles soient gérer par la
classe ici ? C-à-d celle qui l'utilise, et qui sait quand elle
n'en a plus besoin.

-- Et quel est la politique vis-à-vis des copies et de
l'affectation ? Est-ce qu'elles sont supportées, et si oui,
avec quelle sémantique, copie profonde, ou une sémantique de
référence ? (Typiquement, quand on a une hièrarchie
polymorphique, on interdit l'affectation, et souvent la
copie, ou au moins, on force à ce qu'elle passe par une
fonction virtuelle, du genre clone().)


Les copies sont interdites et l'affectation est faite par un
memcopy de la mémoire après test de compatibilité.


C'est un peu bizarre que l'affectation soit supportée, et non la
copie. L'inverse est fréquente, mais c'est la première fois que
je vois ce cas-ci.

Je suis aussi un peu scéptique en ce qui concerne l'utilité de
l'affectation d'un objet polymorphique, étant donné qu'on ne
peut pas changer le type dynamiquement. En général, quand il le
faut (ce qui est rarement), il faut l'idiome lettre/enveloppe,
pour donner une sémantique de valeur à l'objet abstrait.

-- En fait, quel est le but de l'héritage ici, dans la mesure
où il n'y a pas de fonctions virtuelles ? Voire même, quel
est le but de la classe ? Quel est son rôle dans
l'application ? Que sont ses responsibilités ?


Le but de l'héritage est de dissocier les objects User des objects DSP.
En fait les objets User sont stockée en RAM du PC et les DSP dans du
hard ! Mais je ne peux pas faire de GetMem virtuel à cause du type de
retour.


Un getMem qui renvoie un void* ?

En fait, je me démande 1) dans quelle mesure il faut introduire
les types dans l'hièrarchie, et 2) s'il le faut, est-ce qu'une
solution mixin ne serait pas la meilleur solution.

En ce qui concerne le premier, je vois bien l'intérêt d'une
classe abstraite avec deux classes dérivées pour les sources
différentes. C'est l'intérêt d'une hièrarchie pour les types qui
me pose la question. En général, quand on utilise une classe de
base, c'est avec l'idée qu'on peut substituer n'importe quel
type dérivé à peu près partout. Or, tel que c'est partie chez
toi, il faut bien que tous les types dérivés (en ce qui concerne
le type de données) soient identiques ; tu ne peux pas
additionner les images 8 bits et les images 32 bits. Ou est-ce
que je me trompe là-dessus. En somme, il ne m'est pas trop clair
quel rôle le polymorphisme joue par rapport aux types.

Si, en revanche, il faut bien un polymorphisme sur les types
d'image, et non seulement sur les sources, je verrais bien une
solution à base de mixin. Grosso modo, tu dévinis une classe de
base abstract (probablement Image, mais surtout pas Object, qui
ne veut rien dire). Ensuite, tu implémentes les aspects qui
dépend du type dans une classe templatée (probablement) qui
dérive d'Image. Cette classe est elle aussi abstraite, parce
qu'elle n'implémente que les aspects qui dépend du type, et
laisse virtuelle pûre les fonctions qui dépend de la source.
Ensuite, tu définis deux classes dérivées d'Image qui implémente
chacune une des politiques de la source. Finalement, pour faire
des classes concrètes finale, c'est encore une classe template à
deux paramètres, à peu près :

template< typename DataType, typename SourceImpl >
class ConcreteImage : public ImageData< DataType >
, public SourceImpl
{
// Dans certains cas, on n'a même besoin de
// rien ici. Mais pas toujours.
} ;

(Du point de vue conceptuelle, on pourrait arguer que l'héritage
ici doit être « public virtual Image, private ImageData, private
SourceImpl », du fait que l'héritage de ImageData et de
SourceImpl, c'est un héritage d'implémentation, et non
d'interface, et que l'interface dont on veut hériter, c'est bien
celle de Image. Mais j'avoue que je ne suis pas aussi puriste.)

template<typename Type>
class ObjectUser : public Object
{
...
private :
Type *_mem;
}

template<typename Type>
class ObjectDSP : public Object;
{
...
private :
Type *_mem;
}

template<typename Type>
class ImageUser : public ObjectUser<Type>;

template<typename Type>
class ImageDSP : public ObjectDSP<Type>;

Donc en résumé, la classe de base est Object qui est dérivée
en ObjectUser et ObjectDSP. ObjectUser est dérivée en
ImageUser et d'autre classes du même genre. idem pour
ObjectDSP

J'ai utilisé des templates car je dois utiliser les types u8,
s8, u16, s16, u32, s32 et float.
Le but est de faire des calculs mathématiques sur des images
entre autre.


C'est peut-être le but du programme, mais quel rapport aux
classes présentées ici ? Elles ne font pas de calculs.


C'est normal que les classes ne font pas de calcul car il y a trop de
cas à gérer directement : il y a 3 types d'Objects (User, PC et DSP), 7
types de donnée (u8, s8, u16, s16, u32, s32 et float), environ 5 Objects
différents (Image, scalaire, vecteur ...)
Donc pour écrire toutes les additions entre les 3 types d'objets ça en
devient impossible.
Donc la solution était de faire une classe math qui s'en charge en
'bricolant' sur le type des données.


C'est donc bien de l'aiguillage multiple qu'il te faut.

Malheureusement (dans ton cas, au moins), le langage ne le
supporte pas directement. Ou bien, on utilise le modèle
visiteur, et il faut quand même que chaque classe en connaisse
toutes les autres, ou bien, il faut une solution à base de map,
comme j'ai présenté. Dans l'ensemble, la solution visiteur est
plus simple à mettre en oeuvre, et plus robuste, mais impose une
rétouche à toutes les classes dans l'hièrarchie chaque fois
qu'on ajoute une nouvelle classe. Elle a aussi l'avantage qu'on
peut ne prendre en compte qu'une classe intermédiaire dans
l'hièrarchie, utiliser la même fonction pour toutes les
combinaisons u32 + u8, par exemple, plutôt que d'avoir aum
minimum une entrée pour chaque combinaison de User-u32, PC-u32,
DSP-u32 et User-u8, PC-u8, DSP-u8. (C-à-d une « entrée », plutôt
que neuf.)

Après réflexion, je vais passer la classe Object en template
afin d'avoir un pointeur sur le bon type de donnée et éviter
le void* (pour _mem).


Voilà la réponse à ma première question : est-ce qu'il fallait
un polymorphisme sur les types, ou non ?

[...]
Pour conclure, j'enlève les noeuds et donc je peux mettre mes
Objects en template et faire une classe Math template dans
laquelle je teste avec des dynamic_cast que les Objects sont
compatibles. Du coup le modèle marche beaucoup mieux.


C'est sûr que si on n'a pas besoin d'une généricité dynamique
sur le type de données, le problème est beaucoup plus facile.

--
James Kanze GABI Software
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
sej
sej wrote:
sej wrote:

-- Qui est responsable de la mémoire pointé par _mem ? Si tu
n'utilises pas le collecteur de Boehm, il faut bien s'en
occuper.


Il y a une classe qui gère les allocations et désallocations mémoires.


Est-ce que ça ne serait pas mieux qu'elles soient gérer par la
classe ici ? C-à-d celle qui l'utilise, et qui sait quand elle
n'en a plus besoin.



Je préfère séparer la gestion mémoire dans d'autre classe et faire appel
à ces classes dans le constructeur et destructeur des Objects.


-- Et quel est la politique vis-à-vis des copies et de
l'affectation ? Est-ce qu'elles sont supportées, et si oui,
avec quelle sémantique, copie profonde, ou une sémantique de
référence ? (Typiquement, quand on a une hièrarchie
polymorphique, on interdit l'affectation, et souvent la
copie, ou au moins, on force à ce qu'elle passe par une
fonction virtuelle, du genre clone().)


Les copies sont interdites et l'affectation est faite par un
memcopy de la mémoire après test de compatibilité.


C'est un peu bizarre que l'affectation soit supportée, et non la
copie. L'inverse est fréquente, mais c'est la première fois que
je vois ce cas-ci.

Je suis aussi un peu scéptique en ce qui concerne l'utilité de
l'affectation d'un objet polymorphique, étant donné qu'on ne
peut pas changer le type dynamiquement. En général, quand il le
faut (ce qui est rarement), il faut l'idiome lettre/enveloppe,
pour donner une sémantique de valeur à l'objet abstrait.



En effet c'est une partie à éclaircir étant donné que les Objects sont
soit en RAM PC soit dans la partie hard.


-- En fait, quel est le but de l'héritage ici, dans la mesure
où il n'y a pas de fonctions virtuelles ? Voire même, quel
est le but de la classe ? Quel est son rôle dans
l'application ? Que sont ses responsibilités ?


Le but de l'héritage est de dissocier les objects User des objects DSP.
En fait les objets User sont stockée en RAM du PC et les DSP dans du
hard ! Mais je ne peux pas faire de GetMem virtuel à cause du type de
retour.


Un getMem qui renvoie un void* ?

En fait, je me démande 1) dans quelle mesure il faut introduire
les types dans l'hièrarchie, et 2) s'il le faut, est-ce qu'une
solution mixin ne serait pas la meilleur solution.



1) Le problème est de connaître le vrai type des données afin de
travailler dessus. Sinon tu proposes le void* je suppose ?

2) La solution mixin peut etre intéressante mais elle fait appel à de
l'héritage multiple ce que je ne souhaite pas mettre en oeuvre.


En ce qui concerne le premier, je vois bien l'intérêt d'une
classe abstraite avec deux classes dérivées pour les sources
différentes. C'est l'intérêt d'une hièrarchie pour les types qui
me pose la question. En général, quand on utilise une classe de
base, c'est avec l'idée qu'on peut substituer n'importe quel
type dérivé à peu près partout. Or, tel que c'est partie chez
toi, il faut bien que tous les types dérivés (en ce qui concerne
le type de données) soient identiques ; tu ne peux pas
additionner les images 8 bits et les images 32 bits. Ou est-ce
que je me trompe là-dessus. En somme, il ne m'est pas trop clair
quel rôle le polymorphisme joue par rapport aux types.


En fait si je souhaite additionner des 8 bits avec des 32 bits mais je
veux tout contrôler car il y aura des cas interdits.
Si j'implémente des méthodes du genre :
template<typename TypeR, typename TypeA, typename TypeB>
Math::Add2Image(Object<TypeR>&r, Object<TypeA>&a, Object<TypeB>&b)

Je peux vérifier que mes 3 types sont compatibles et je peux additionner
tout ce que je veux. Le problème étant d'ajouter des types dérivés ?
Je comptais les caster au niveau supérieur afin de traiter uniquement
des Objects.

Si, en revanche, il faut bien un polymorphisme sur les types
d'image, et non seulement sur les sources, je verrais bien une
solution à base de mixin. Grosso modo, tu dévinis une classe de
base abstract (probablement Image, mais surtout pas Object, qui
ne veut rien dire). Ensuite, tu implémentes les aspects qui
dépend du type dans une classe templatée (probablement) qui
dérive d'Image. Cette classe est elle aussi abstraite, parce
qu'elle n'implémente que les aspects qui dépend du type, et
laisse virtuelle pûre les fonctions qui dépend de la source.
Ensuite, tu définis deux classes dérivées d'Image qui implémente
chacune une des politiques de la source. Finalement, pour faire
des classes concrètes finale, c'est encore une classe template à
deux paramètres, à peu près :

template< typename DataType, typename SourceImpl >
class ConcreteImage : public ImageData< DataType >
, public SourceImpl
{
// Dans certains cas, on n'a même besoin de
// rien ici. Mais pas toujours.
} ;

(Du point de vue conceptuelle, on pourrait arguer que l'héritage
ici doit être « public virtual Image, private ImageData, private
SourceImpl », du fait que l'héritage de ImageData et de
SourceImpl, c'est un héritage d'implémentation, et non
d'interface, et que l'interface dont on veut hériter, c'est bien
celle de Image. Mais j'avoue que je ne suis pas aussi puriste.)

template<typename Type>
class ObjectUser : public Object
{
...
private :
Type *_mem;
}

template<typename Type>
class ObjectDSP : public Object;
{
...
private :
Type *_mem;
}

template<typename Type>
class ImageUser : public ObjectUser<Type>;

template<typename Type>
class ImageDSP : public ObjectDSP<Type>;

Donc en résumé, la classe de base est Object qui est dérivée
en ObjectUser et ObjectDSP. ObjectUser est dérivée en
ImageUser et d'autre classes du même genre. idem pour
ObjectDSP

J'ai utilisé des templates car je dois utiliser les types u8,
s8, u16, s16, u32, s32 et float.
Le but est de faire des calculs mathématiques sur des images
entre autre.


C'est peut-être le but du programme, mais quel rapport aux
classes présentées ici ? Elles ne font pas de calculs.


C'est normal que les classes ne font pas de calcul car il y a trop de
cas à gérer directement : il y a 3 types d'Objects (User, PC et DSP), 7
types de donnée (u8, s8, u16, s16, u32, s32 et float), environ 5 Objects
différents (Image, scalaire, vecteur ...)
Donc pour écrire toutes les additions entre les 3 types d'objets ça en
devient impossible.
Donc la solution était de faire une classe math qui s'en charge en
'bricolant' sur le type des données.


C'est donc bien de l'aiguillage multiple qu'il te faut.

Malheureusement (dans ton cas, au moins), le langage ne le
supporte pas directement. Ou bien, on utilise le modèle
visiteur, et il faut quand même que chaque classe en connaisse
toutes les autres, ou bien, il faut une solution à base de map,
comme j'ai présenté. Dans l'ensemble, la solution visiteur est
plus simple à mettre en oeuvre, et plus robuste, mais impose une
rétouche à toutes les classes dans l'hièrarchie chaque fois
qu'on ajoute une nouvelle classe. Elle a aussi l'avantage qu'on
peut ne prendre en compte qu'une classe intermédiaire dans
l'hièrarchie, utiliser la même fonction pour toutes les
combinaisons u32 + u8, par exemple, plutôt que d'avoir aum
minimum une entrée pour chaque combinaison de User-u32, PC-u32,
DSP-u32 et User-u8, PC-u8, DSP-u8. (C-à-d une « entrée », plutôt
que neuf.)

Après réflexion, je vais passer la classe Object en template
afin d'avoir un pointeur sur le bon type de donnée et éviter
le void* (pour _mem).


Voilà la réponse à ma première question : est-ce qu'il fallait
un polymorphisme sur les types, ou non ?


Je pense que oui. Mais pourquoi mettre du void* ? Comment s'en
débrouiller ensuite pour accéder aux données ?

[...]
Pour conclure, j'enlève les noeuds et donc je peux mettre mes
Objects en template et faire une classe Math template dans
laquelle je teste avec des dynamic_cast que les Objects sont
compatibles. Du coup le modèle marche beaucoup mieux.


C'est sûr que si on n'a pas besoin d'une généricité dynamique
sur le type de données, le problème est beaucoup plus facile.


Pour l'instant ma solution est :
Object<Type> avec un pointeur <Type>* _mem dans la classe.
Et des classes dérivée templates.
J'implémente aussi des méthodes de calculs dans la classe Math en
template sur des Object<> uniquement.
Concernant les affectations, je pense qu'il est plus simple de créer une
méthode equal dans Math et d'interdire recopie et operator= dans
Object<> et ses dérivés.
Qu'en penses tu ?


--
James Kanze GABI Software
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
kanze
sej wrote:
sej wrote:
sej wrote:





[...]
-- En fait, quel est le but de l'héritage ici, dans la mesure
où il n'y a pas de fonctions virtuelles ? Voire même, quel
est le but de la classe ? Quel est son rôle dans
l'application ? Que sont ses responsibilités ?


Le but de l'héritage est de dissocier les objects User des
objects DSP. En fait les objets User sont stockée en RAM du
PC et les DSP dans du hard ! Mais je ne peux pas faire de
GetMem virtuel à cause du type de retour.


Un getMem qui renvoie un void* ?

En fait, je me démande 1) dans quelle mesure il faut
introduire les types dans l'hièrarchie, et 2) s'il le faut,
est-ce qu'une solution mixin ne serait pas la meilleur
solution.


1) Le problème est de connaître le vrai type des données afin
de travailler dessus. Sinon tu proposes le void* je suppose ?


Au niveau où le type n'est pas connu, void* me semble le plus
indiqué. En revanche, c'est un niveau que j'éviterais s'il n'est
pas nécessaire.

2) La solution mixin peut etre intéressante mais elle fait
appel à de l'héritage multiple ce que je ne souhaite pas
mettre en oeuvre.


C'est un outil puissant dans certains cas. Il ne faut pas en
abuser, mais quand on veut avoir des objets dont le type dépend
de deux (ou plus) aspects assez séparés, comme semble être le
cas ici, le résultat avec mixin est en général ce qui est le
plus simple à comprendre et à implémenter.

[...]
Après réflexion, je vais passer la classe Object en template
afin d'avoir un pointeur sur le bon type de donnée et éviter
le void* (pour _mem).


Voilà la réponse à ma première question : est-ce qu'il fallait
un polymorphisme sur les types, ou non ?


Je pense que oui. Mais pourquoi mettre du void* ?


Parce que c'est le type qui sert habituellement quand on ne
connaît pas le type.

Comment s'en débrouiller ensuite pour accéder aux données ?


static_cast.

En fait, je crois que je laisserais quand même le pointeur aux
données dans une classe dérivée qui en connaît le type. Si je
veux un pointeur à u32, par exemple, j'utiliserais d'abord un
dynamic_cast sur l'objet d'image (qui contient le pointeur).
C'est plus bétoné contre les erreurs de typage. C-à-d qu'à la
place de :
u32* p = static_cast< u32* >( obj->getMem() ) ;
quelque chose comme :
TypedImage< u32 >* typedObj
= dynamic_cast< TypedImage< u32 > >( obj ) ;
if ( typedObj == NULL ) {
// Traitement d'erreur...
}
u32* p = typedObj->getMem() ;
Il n'y aurait pas de getMem() dans la classe de base, seulement
dans les images typés.

[...]
Pour conclure, j'enlève les noeuds et donc je peux mettre mes
Objects en template et faire une classe Math template dans
laquelle je teste avec des dynamic_cast que les Objects sont
compatibles. Du coup le modèle marche beaucoup mieux.


C'est sûr que si on n'a pas besoin d'une généricité dynamique
sur le type de données, le problème est beaucoup plus facile.


Pour l'instant ma solution est :
Object<Type> avec un pointeur <Type>* _mem dans la classe.
Et des classes dérivée templates.

J'implémente aussi des méthodes de calculs dans la classe Math
en template sur des Object<> uniquement. Concernant les
affectations, je pense qu'il est plus simple de créer une
méthode equal dans Math et d'interdire recopie et operator=
dans Object<> et ses dérivés.

Qu'en penses tu ?


Que l'affectation et la copie risque d'être les opérations
chères, parce qu'a priori, on s'attendrait à une copie profonde.
Et qu'avec le polymorphisme, on ne travaille de toute façon
qu'avec des pointeurs et des références. Mais qu'une fonction
virtuelle de clone, qui se base sur un constructeur de copie
privée, pourrait éventuellement être utile, quand on veut
réelement une copie profonde, même sans savoir nécessairement le
type.

Quant à une fonction equal() dans une autre classe : d'après le
nom, je m'attendrais à une comparaison, non une affectation. Et
que ce soit une comparaison ou une affectation, il faut qu'elle
soit membre de la classe en question pour jouir de la résolution
dynamique de la fonction. Sinon, il faut que tu réimplémentes
l'aiguillage dynamique qui fait cependant partie du langage.

--
James Kanze GABI Software
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