OVH Cloud OVH Cloud

Héritage et template

17 réponses
Avatar
Guillaume Gourdin
Bonjour à tous. Voilà, j'ai une classe template que je voudrais instancier
selon le type du template :

template <class T> class Pipo {};

// plus loin...
Pipo * pipo;
switch (Type)
{
case 0 : pipo = new Pipo<float>(); break;
case 1 : pipo = new Pipo<int>(); break;
case 2 : pipo = new Pipo<short>(); break;
}

Mais ce code ne compile pas sous VC6, car, grosso modo, le compilateur me
dit qu'il ne peut pas convertir de Pipo<float>* vers Pipo, idem pour les int
et les shorts.

Avez-vous des suggestions pour pallier ce genre de problème et avoir un
pointeur de template génétique quelques soit le type du template ? J'avais
pensé à créer une classe PipoFloat héritant de Pipo<float>, mais ça ne
semble pas résoudre le problème.

Merci.

7 réponses

1 2
Avatar
kanze
"Michaël Monerau" wrote in message
news:<unXdb.181378$...
wrote:
Le problème avec cette méthode, c'est que tu ne pas retourner un
objet du type du template par exemple (les fonctions de PipoBase ne
peuvent pas savoir qu'on va dériver en template...).

D'ailleurs, si quelqu'un a une solution pour palier à ce dernier
problème, je suis preneur :-)


Je crois que boost::any pourrait faire l'affaire. Mais je me
poserais des questions : pourquoi ? Qu'est-ce qu'on va réelement
faire avec pipo ?


J'avais eu le pb avec un système que je voulais implémenter : je
voulais faire une variable d'un type que l'on veut (int, float, types
persos...), et duquel on pourrait avoir la valeur. Comme un type
intégral en fait, mais encapsulé pour pouvoir traiter un vector de
tels objets et les manipuler chacun pareil.


C'est difficile à dire avec si peu d'informations, mais a priori, j'ai
l'impression que tu cherches à utiliser l'héritage quand c'est les
templates qui conviennent.

Voilà un code qui résume un peu (en raccourci) :

class UVarBase
{
public:
virtual xxx GetValue () const = 0;
};

template <class T>
class UVar
{
public:
typedef const_ref_type const T&;

virtual xxx GetValue () const = 0;
// je voudrais :
virtual const T& GetValue () const = 0;
};


Mais je ne vois toujours pas l'intérêt de UVarBase et des fonctions
virtuelles. Quoique tu fasses, il faut que l'utilisateur sache le type
réel. Alors, le template suffit ; il n'y a pas besoin de la base et de
l'héritage.

Bon, je n'ai pas mis les opérateurs et les setteurs... Mais l'idée y
est. Dans la classe de base, on ne peut pas connaître le type template
final. Du coup, la fonction GetValue ne peut pas renvoyer le bon type
directement. Alors moi j'avais fait renvoyer un void*, que je
recastais ensuite en faisant confiance au programmeur qui appelait la
fonction :

template <class T>
typename UVar<T>::const_ref_type UVarValue (const UVar<T>* pUVar)
{
return *(reinterpret_cast<UVar<T>::const_pointer_type>(pUVar->GetValue()));
}


Et si l'utilisateur se trompait du type ?

Il y a de très rares cas où une telle construction se justifie. Dans de
tels cas, je me rebattrais sur boost::any.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16



Avatar
kanze
"Michaël Monerau" wrote in message
news:<yqXdb.181419$...

(en vérité, c'était pour un autre usage : une suite d'options dans un
de mes programmes... Mais bon).

Peut-être existe-t-il un meilleur idiôme pour faire ce que je voulais...

Mais ça me permettait d'avoir un vector<UVarBase*> et de pouvoir gérer
toutes les options facilement (à partir de leur nom). Mais ça impose
de faire très attention au paramètre template donne à UVarValue
(UVarBase*).


C'est alors pour gérer les options. Tu pourrais régarder ce que j'ai
fait dans GB_CommandLine/GB_Option à ma site, par exemple. Mais la
principe est simple : il y a bel et bien une classe de base (GB_Option),
qui a une fonction virtuelle pure « set ». Mais cette fonction ne prend
pas de paramètres -- la classe de base GB_Option collabore avec
GB_CommandLine pour pouvoir fournir les chaînes de la ligne de commande,
si l'option a besoin d'un paramètre. Et il n'y a pas de getter du tout,
au niveau de la classe de base -- l'utilisateur d'une option donnée doit
bien savoir le type de l'option, et avoir accès à la classe dérivée.

Note aussi que certaines options n'ont pas de valeur dans le sens
classique, mais provoque une action, qui peut changer l'état de
plusieurs variables.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16

Avatar
Michaël Monerau
wrote:
"Michaël Monerau" wrote in message
news:<yqXdb.181419$...

(en vérité, c'était pour un autre usage : une suite d'options dans un
de mes programmes... Mais bon).

Peut-être existe-t-il un meilleur idiôme pour faire ce que je
voulais...

Mais ça me permettait d'avoir un vector<UVarBase*> et de pouvoir
gérer toutes les options facilement (à partir de leur nom). Mais ça
impose
de faire très attention au paramètre template donne à UVarValue
(UVarBase*).


C'est alors pour gérer les options. Tu pourrais régarder ce que j'ai
fait dans GB_CommandLine/GB_Option à ma site, par exemple. Mais la
principe est simple : il y a bel et bien une classe de base
(GB_Option), qui a une fonction virtuelle pure « set ». Mais cette
fonction ne prend pas de paramètres -- la classe de base GB_Option
collabore avec GB_CommandLine pour pouvoir fournir les chaînes de la
ligne de commande, si l'option a besoin d'un paramètre. Et il n'y a
pas de getter du tout, au niveau de la classe de base --
l'utilisateur d'une option donnée doit bien savoir le type de
l'option, et avoir accès à la classe dérivée.

Note aussi que certaines options n'ont pas de valeur dans le sens
classique, mais provoque une action, qui peut changer l'état de
plusieurs variables.


Je vais regarder tes classe GB_Option et GB_CommandLine, je comprendrai
sûrement mieux de quoi tu parles. Mais je ne saisis pas comment une option
sans getteur peut être utile. Comment récupérer sa valeur ? (par exemple,
l'option -o de gcc, tu dois bien recueillir le chemin qu'il y a derrière...)
--
<=- Michaël "Cortex" Monerau -=>


Avatar
Michaël Monerau
wrote:
J'avais eu le pb avec un système que je voulais implémenter : je
voulais faire une variable d'un type que l'on veut (int, float, types
persos...), et duquel on pourrait avoir la valeur. Comme un type
intégral en fait, mais encapsulé pour pouvoir traiter un vector de
tels objets et les manipuler chacun pareil.


C'est difficile à dire avec si peu d'informations, mais a priori, j'ai
l'impression que tu cherches à utiliser l'héritage quand c'est les
templates qui conviennent.


L'héritage est utile dans mon cas. En effet, je veux pouvoir stocker toutes
les options dans le même vector<>. Comme ça, l'utilisateur tape à la console
"option3 = 45". Je cherche dans mon vector<> de base* (par un GetName() que
base contient en virtuelle pure) le nom Option3. Ensuite, j'aimerais faire
un 'set (45)'. Pour l'instant, pour le setteurs et le getteur, je passe par
du void* casté dans le type que le programmeur donne... Ce qui n'est pas
très safe.

Pareil pour le get. S'il tape "option3" à la console, je veux que la valeur
s'affiche. Ici, 'print()' pourrait être appelée, mais si je veux *vraiment*
la valeur, dans un int, le seul moyen que je vois serait de passer par ma
fonction UVarValue.

Mais je ne vois toujours pas l'intérêt de UVarBase et des fonctions
virtuelles. Quoique tu fasses, il faut que l'utilisateur sache le type
réel. Alors, le template suffit ; il n'y a pas besoin de la base et de
l'héritage.


Pour faire mon vector<UVarBase*>.

Bon, je n'ai pas mis les opérateurs et les setteurs... Mais l'idée y
est. Dans la classe de base, on ne peut pas connaître le type
template final. Du coup, la fonction GetValue ne peut pas renvoyer
le bon type directement. Alors moi j'avais fait renvoyer un void*,
que je
recastais ensuite en faisant confiance au programmeur qui appelait la
fonction :

template <class T>
typename UVar<T>::const_ref_type UVarValue (const UVar<T>* pUVar)
{
return
*(reinterpret_cast<UVar<T>::const_pointer_type>(pUVar->GetValue()));
}


Et si l'utilisateur se trompait du type ?


C'est justement le gros risque, qui fait boooom. C'est pourquoi une solution
par typedef (pour chaque UVar créée, un typedef à la main est fait, et lui
seul devrait être utilisé comme argument template pour cette UVar...).

Il y a de très rares cas où une telle construction se justifie. Dans
de tels cas, je me rebattrais sur boost::any.


Je ne connais pas, mais j'irai sûrement voir :-)


Avatar
kanze
"Michaël Monerau" wrote in message
news:<XAgeb.200032$...

Je vais regarder tes classe GB_Option et GB_CommandLine, je
comprendrai sûrement mieux de quoi tu parles. Mais je ne saisis pas
comment une option sans getteur peut être utile. Comment récupérer sa
valeur ? (par exemple, l'option -o de gcc, tu dois bien recueillir le
chemin qu'il y a derrière...)


L'option sert à deux endroits bien distincts. D'abord, dans
GB_CommandLine, où on la detecte, et où on appelle la fonction virtuelle
pure GB_Option::set. Et deuxièmement, dans un bout du code qui se sert
de l'option. La gestion des options dans GB_CommandLine est bien
générique -- GB_CommandLine reconnaît qu'il y a une option dans la ligne
de commande (grace à '-' au début du paramètre), et démande aux
GB_Option inscrites si elles peuvent en faire quelque chose. Si le
GB_Option dit oui, il appelle sa fonction set.

À l'endroit où on se sert de l'option, en revanche, on sait bien de quoi
il s'agit. On aurait quelque chose du genre :

GB_BooleanOption v( 'v' ) ;
GB_NumericOption n( 'n' ) ;

// ...

if ( v ) {
std::vector< int > a( n ) ;
// ...
}

Les classes dérivées n'ont pas seulement les getter, elles ont des
conversions implicites vers le type voulu, pour que le code utilisateur
peut se servir d'un GB_BooleanOption comme si c'était un bool, un
GB_NumericOption comme si c'était un int, etc.

C'est une solution que je trouve assez agréable pour de petits et moyens
programmes.

Pour des programmes plus grands, avec des fichiers de configuration, je
me suis aussi servi d'une classe Configuration, qui maintenait bien un
map<string,string> avec les options, toutes stockées en forme de chaîne.
Pour en accéder, il y avait une fonction template membre, qu'on appelait
avec le type explicit :

std::vector< int > a(
Configuration::instance().get< int >( "n" ).elseDefaultTo( 100 ) ) ;

(Configuration::get renvoie un GB_Fallible< T >.)

On note que la version templatée de Configuration::get appelle, lui,
Configuration::get< std::string >, qui lui est spécialisée
explicitement, et utilise std::istringstream pour en obtenir le bon
type. Ce qui revient un peu à la suggestion de Christophe, je crois.

Cette méthode a une faiblesse que je vois : on ne detecte des erreurs de
format dans le fichier de configuration que lorsqu'on essaie de lire la
valeur du Configuration. Alors, on peut bien logger l'erreur, mais des
informations du genre numéro de la ligne dans le fichier de
configuration, voire même le nom du fichier (parce qu'on supportait des
include), n'y sont plus. C'est moyennement genant, mais en affichant la
clé dans le message d'erreur, l'utilisateur arrive en général à trouver
l'erreur assez vite quand même.

(En fait, quand je l'ai fait, mon compilateur n'était pas assez fort en
templates pour ce genre de chose. J'ai donc choisi de ne supporter qu'un
nombre limité de types, dont j'ai généré les getter au moyen d'un
macro.)

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16

Avatar
kanze
"Michaël Monerau" wrote in message
news:<VFgeb.200064$...
wrote:
J'avais eu le pb avec un système que je voulais implémenter : je
voulais faire une variable d'un type que l'on veut (int, float,
types persos...), et duquel on pourrait avoir la valeur. Comme un
type intégral en fait, mais encapsulé pour pouvoir traiter un
vector de tels objets et les manipuler chacun pareil.


C'est difficile à dire avec si peu d'informations, mais a priori,
j'ai l'impression que tu cherches à utiliser l'héritage quand c'est
les templates qui conviennent.


L'héritage est utile dans mon cas. En effet, je veux pouvoir stocker
toutes les options dans le même vector<>. Comme ça, l'utilisateur tape
à la console "option3 = 45". Je cherche dans mon vector<> de base*
(par un GetName() que base contient en virtuelle pure) le nom Option3.
Ensuite, j'aimerais faire un 'set (45)'. Pour l'instant, pour le
setteurs et le getteur, je passe par du void* casté dans le type que
le programmeur donne... Ce qui n'est pas très safe.

Pareil pour le get. S'il tape "option3" à la console, je veux que la
valeur s'affiche. Ici, 'print()' pourrait être appelée, mais si je
veux *vraiment* la valeur, dans un int, le seul moyen que je vois
serait de passer par ma fonction UVarValue.


Dans ce cas-là, je stockerais tout sous forme de chaîne, tout bêtement,
et je n'effectuerais la conversion (au moyen d'un istringstream) que
quand je voulais réelement me servir de l'option.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16



Avatar
Michaël Monerau
wrote:
"Michaël Monerau" wrote in message
news:<XAgeb.200032$...

Je vais regarder tes classe GB_Option et GB_CommandLine, je
comprendrai sûrement mieux de quoi tu parles. Mais je ne saisis pas
comment une option sans getteur peut être utile. Comment récupérer sa
valeur ? (par exemple, l'option -o de gcc, tu dois bien recueillir le
chemin qu'il y a derrière...)



<snip>

Merci pour toutes ces explications. Je m'y référerai plus tard quand j'aurai
ce type de chose à implémenter.
C'est en effet un moyen de faire qui me plaît beaucoup.
--
<=- Michaël "Cortex" Monerau -=>


1 2