OVH Cloud OVH Cloud

Aide pour realiser une classe de manipualtion de donnees

45 réponses
Avatar
Aurélien REGAT-BARREL
Bonjour,
Ca parrait long, mais c'est vite lu :-)

J'ai un certain nombre de classes ABC, DEF, GHI, ... qui ont la
particularité d'avoir une certain nombre (presque tout le temps 3) de
"valeurs" toutes de type double. En fait, la seule chose qui différencies
ces classes est le nom de ces valeurs :

class ABC
{
public:
double A() const { return this->a; }
double B() const { return this->b; }
double C() const { return this->c; }
private:
double a;
double b;
double c;
};

class DEF
{
public:
double D() const { return this->d; }
double E() const { return this->e; }
double F() const { return this->f; }
private:
double d;
double e;
double f;
};

etc...
C'est utile de les différencier, car ces valeurs n'ont pas la même
signification, et le typage est nécessaire.
Jusque là tout va bien, tout est calculé correctement.

Probleme :
Je veux dessiner ces valeurs dans un graphe, 1 valeur au choix sur chaque
axe.
Par exemple, dessiner A() et B() sur mon graphe.
Je voudrais donc faire un truc générique, et pas XX fonctions pour chaque
type.
J'ai voulu faire hériter toutes ces classes d'une même classe de base :
Triplet.

class Triplet
{
public:
virtual double Value( int Index ) const = 0;
};

Ainsi je file mon ABC ou DEF ou XYZ à mon graphe et je lui dit "dessine moi
la valeur 1 et 2 et il fait Value( 1 ) et Value ( 2 ) pour les récupérer.

class ABC : public Triplet
{
// idem que plus haut

public:
double Value( int Index )
{
switch ( Index )
{
case 0 : return this->A();
case 1 : return this->B();
case 2 : return this->C();
}
// exception
}
};

Mais Triplet est abstraite, et je ne peux pas contruire un
std::vector<Triplet> à passer pour dessiner.
Je compte du coup re-écrire toutes les classes dans ce style :

class Triplet
{
public:
virtual double Value( int Index ) const { return this->values.at(
Index ); }
private:
std::vector<double> values;
};

class ABC : public Triplet
{
public:
double A() const { return this->values[ 0 ]; }
double B() const { return this->values[ 1 ]; }
double C() const { return this->values[ 2 ]; }
};

Y'a-t-il une meilleure solution ?
Par exemple :

class ABC : public std::vector<double>
{
public:
double A() const { return this->at( 0 ); }
double B() const { return this->at( 1 ); }
double C() const { return this->at( 2 ); }
};

Merci de m'avoir lu.

--
Aurélien REGAT-BARREL

10 réponses

1 2 3 4 5
Avatar
kanze
"Aurélien REGAT-BARREL" wrote in
message news:<40d6a045$0$3992$...
Ça ne me semble pas mauvais.

Ceci dit, j'ai lu la reste de la discussion, où tu dis de ne pas
aimer les pointeurs. Or, en C++, qu'on les aime ou non, le
polymorphisme implique l'utilisation des pointeurs. Et un certain
nombre d'autres choses.


Peux-tu détailler un petit peu. Pourquoi cela implique-t-il les
pointeurs ?


Parce qu'en C++, un appel n'est en fait polymorphe que lorsqu'il passe
par un pointeur ou par une référence. Le type d'un objet connu du
compilateur est fixé lors de la compilation ; tant que tu n'as pas de
pointeur ni de référence (qui agit un peu comme un pointeur à cet
égard), le type que voit le compilateur, c'est le type réel de l'objet.

C'est la base de ton problème, d'ailleurs. Si tu fais un
std::vector<Triplet>, le type que voit le compilateur, c'est Triplet. Et
puisqu'il n'y a pas de pointeur ni de référence, le type réel dynamique
est aussi Triplet.

Le C++ n'a pas rénouncé ses origines de C au point que tout soit en fait
un pointeur. Il supporte aussi très bien des types « valeur », qu'on
copie, affecte, etc. Sans pointeur ni référence. Mais dans la pratique,
il faut choisir : ou bien, on a un objet polymorphique, qui est la
plupart du temps alloué dynamiquement, qu'on manipule à travers des
pointeurs et des références, et qu'on ne copie ni affecte pas ; ou bien,
on a un objet à sémantique de valeur, qu'on alloue la plupart du temps
statiquement ou sur la pile, qu'on copie et qu'on affecte à droit et à
gauche, mais qui n'est pas polymorphique.

Et évidemment, ce que je viens de dire, c'est très schématique, il y a
bien d'exceptions, et dans la pratique, il y a aussi des exceptions.
Mais c'est un bon point du départ.

Si tes classes ABC et DEF sont réelement des valeurs, avec une
sémantique de valeur, c'est peut-être que le polymorphisme, même
limité, ne leur convient pas.


Eh bien, si l'on considère que le polymorphisme est à utiliser lors
d'une relation "est un", je pense qu'il est adapté ici.


De point de vue de la conception, je crois bien aussi. Mais il faut voir
d'après ta conception générale si ça passe, et quel en sont les
implications. Est-ce que ABC et DEF sont conceptuellement des Triplets,
ou est-ce que c'est une caractèristique « accidentelle » ? Ou plus
pragmatique, est-ce qu'elles sont deux variations du même type
fondamental, et qu'en général, tu ne t'intéresses pas à leur type
précise ?

Parce que le « est un », ce n'est qu'une face de la médaille. Pour
revenir à ce que j'ai dit avant, on peut (très schématiquement) diviser
les objets en deux catégories : les types à sémantique de valeur, et les
types « entité ».

Les types à sémantique de valeur se comporte à peu près comme des types
de base, tu les copies, les affectes, etc. ; surtout, une copie en vaut
une autre. En C++, il est habituel de déclarer ces types comme variable
locale (sans pointeur ni référence) ; dans le Java bien écrit, ils sont
immutable et final (comme java.langString) ; en C++, personnellement, et
avec un certain nombre d'exceptions, j'ai une tendance aussi à n'avoir
que les opérateurs d'affectation (y compris du genre +=, *=, etc., le
cas échéant) comme fonctions non const. Du point de vu de la conception,
ce sont des objets sans comportement (on les manipule classiquement avec
des fonctions globales, voire même des opérateurs comme +, -), mais dont
toute la signifiance est l'état.

Les types « entité », ce sont des types de modèlisation ; ils ont un
comportement et une identité. On ne les copie pas (avec quelques
exceptions, par exemple pour supporter une fonctionalité de rollback ou
de undo), et on ne les affecte pas, parce qu'une copie n'est pas la même
chose. Du coup, on les manipule surtout à travers des pointeurs ou des
références.

L'exemple type de la différence : Compte et Montant. Qu'on vire 100
Euros sur ton compte en banque, ça t'est parfaitement égal combien de
fois ils ont copié l'objet qui représente les 100 Euros, du moment que
le montant (sa valeur) soit le même. En revanche, lors du virement, tu
préfèrerais sans doute que ça se fait sur ton compte, et non sur une
copie temporaire qui cessera d'exister lorsqu'on quitte la fonction. (Et
j'insiste, tout ça, c'est très schématique. C'est juste pour donner une
idée, et non pour servir de règle absolue.)

Vue que le polymorphisme joue surtout sur le comportement, c'est un peu
logique que ce soit des objets « entité », avec identité, qui soit
polymorphique. Et dans les faits, en C++, du fait qu'on a de vraies
valeurs, avec un typage statique, la polymorphisme se combine très mal
avec les types de valeur. Si je déclare un objet localement Triplet,
e.g.
Triplet t ;
Il a type Triplet. Toujours et sans exception. L'affection ou
l'initialisation à partir d'un autre objet se fait par copie. Et si cet
autre objet n'a pas le type Triplet, même si c'est un type dérivé, on le
convertit en Triplet. Le type d'un objet ne peut jamais changer. Jamais.

Même avec des pointeurs ou des références, le type d'un objet ne peut
jamais changer. Mettons que j'ai un pointeur à un Triplet, qui pointe en
fait un ABC. Je peux changer la valeur du pointeur, pour qu'il pointe à
un DEF. Mais l'objet qui était un ABC reste un ABC. Considérons le
suivant:
Triplet* a = new ABC ;
Triplet* b = new DEF ;
*a = *b ;
Qu'est-ce que cette dernière ligne doit signifier ? Il va bien appeler
la fonction Triplet::operator=( Triplet const& ). Fonction qui peut être
virtuelle. Mais qu'est-ce que cette fonction doit faire ? Quoiqu'elle
fasse, l'objet ne peut pas changer de type. Il reste un ABC.

C'est pour ça que je dis que le polymorphisme se combine mal avec les
objets de valeur.

Il existe des solutions : Coplien en développe plusieurs, dont l'idiome
lettre/envellope que j'utilise personnellement chaque fois que j'ai
réelement besoin que un objet soit à la fois polymorphique et qu'il
supporte l'affectation.

ABC, DEF, ... sont tous des Triplet. La seule chose qui les différencie,
c'est le contexte de leurs valeurs.


Toutes les opérations sur eux sont identique ?

ABC va être les coordonnées d'un point en mètres, DEF en yards, GHI en
kilomètres, etc...


D'accord. J'ai compris.

Est-ce que les valeurs dans le Triplet sont toujours dans la même unité
? C-à-d : quand je crée une Cordonnée en yards, est-ce que constructeur
convertit les yards en mètres avant de les stocker, ou est-ce qu'il
stocke réelement des yards ?

Je pose la question, parce que je me démande ce qui passera is j'essaie
d'utiliser un tableau avec des types différents. Si les coordonnées sont
tous stockées dans la même unité, il ne doit pas y avoir de problème.
Sinon, si par exemple j'ai une Coordonnée mètres, une millimètres, et
une troisième en milles nautiques, il va falloir que je sache lequel est
lequel quand je vais me servir du tableau. Au moins que la classe
Triplet a une fonction virtuelle spéciale, getIndexInMeters, ou quelque
chose du genre. Mais alors, on tombe dans le problème valeur/
polymorphisme. Il existe des solutions, mais c'est beaucoup d'effort
pour éventuellement pas grand chose.

Si en revanche j'ai toujours tout en, disons, mètres, et que ce n'est
qu'au niveau de l'interface que ça change, tout va bien. La conversion
de CoordonnéeEnYards en CoordonnéeDeBase est triviale -- c'est ce qu'on
appelle du slicing, mais dans ce cas-ci, le slicing nous donne
exactement ce qu'on veut. En fait, la « valeur », c'est la
CoordonnéeDeBase. Les classes dérivées peuvent être considérer
simplement comme des interfaces supplémentaires de commodité ;
éventuellement, tout ce qu'elles offriront de plus, c'est des
constructeurs. Mais même si elles en offrent plus, la conversion, c'est
toujours assez simple. Même la copie et l'affectation peuvent marcher
sans problèmes, à condition que dans chaque classe, tu fournis non
seulement l'opérateur d'affectation classique (dont la version générée
par le compilateur convient), mais aussi un opérateur d'affectation :

CoordonnéeEnYards::operator=( CoordonnéeDeBase const& ) ;

(La même chose pour les constructeurs, évidemment.)

C'est un cas particulier où la polymorphisme peut se combiner avec une
sémantique de valeur, à condition de bien comprendre que l'affectation
ne change pas le type ; si tu affectes une CoordonnéeEnKilomètres à une
CoordonnéeEnYards, le CoordonnéeEnYards reste en yards. Et que si tu
fais getIndex( 0 ) sur le CoordonnéeEnKilomètres originale, elle va
renvoyer une valeur bien différente que si tu fais getIndex( 0 ) sur le
CoordonnéeEnYards qui a été affecter. Si
CoordonnéeEnKilomètres::getIndex(0) renvoie 1.0,
CoordonnéeEnYards::getIndex(0) doit renvoyer 1093.611111, pour la même
valeur dans CoordonnéeDeBase (probablement 1000.).

Il est donc important de les différencier pour les manipuler.

Mais pour les afficher, j'ai un graphe qui se calibre automatiquement
en fonction du min et du max.


La question reste : qu'est-ce qui se passe si les types sont mélangés ?
Parce que si tu utilises le polymorphisme classique, on peut les
mélanger, et même s'ils ne sont jamais mélangés aujourd'hui, ils le
seront un jour ou l'autre.

La fonction qui lui donne les Triplets à afficher se charge de mettre
la légende à jour. En fait, mon graphe est un bête graphe générique
qui ne fait que dessiner des Triplet. Passer par Triplet me permet de
ne pas devoir le spécialiser pour chaque nouveau type de Triplet,
réduction du couplage, ...


Tout à fait d'accord. Mais je lui donnerai toujours des triplets dans
une unité préspécifiée.

--
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
Aurélien REGAT-BARREL
Peux-tu détailler un petit peu. Pourquoi cela implique-t-il les
pointeurs ?
[snip]



Je vais méditer sur tes brillantes explications. Merci pour ces précisions.

ABC, DEF, ... sont tous des Triplet. La seule chose qui les différencie,
c'est le contexte de leurs valeurs.


Toutes les opérations sur eux sont identique ?


La seule opération faite sur Triplet est l'affichage.

ABC va être les coordonnées d'un point en mètres, DEF en yards, GHI en
kilomètres, etc...


D'accord. J'ai compris.

Est-ce que les valeurs dans le Triplet sont toujours dans la même unité
? C-à-d : quand je crée une Cordonnée en yards, est-ce que constructeur
convertit les yards en mètres avant de les stocker, ou est-ce qu'il
stocke réelement des yards ?


Non. Pire, mon exemple est mauvais. Il faut plutot voir des mètres, puis des
degrés (angles), etc...
Le point commun est que ces triplets sont le reflet de certaines propriétés,
mais dans des unités qui n'ont rien à voir.
Les Triplet ne sont absolument pas comparables entre eux, les unités ne sont
pas convertibles (distances, angles, pourcentages, ...).

La question reste : qu'est-ce qui se passe si les types sont mélangés ?
Parce que si tu utilises le polymorphisme classique, on peut les
mélanger, et même s'ils ne sont jamais mélangés aujourd'hui, ils le
seront un jour ou l'autre.


C'est incohérent, ça n'a pas de sens, c'est à éviter. Je réalise que la
relation "est un" est juste en théorie, mais fausse en pratique.
Mes Triplets sont en fait des coordonnées 3D dans différents repères. Donc
ABC, DEF, ... sont bien des Triplets dans le sens ou ils ont 3 valeurs. Mais
c'est juste ça le point commun : ils ont 3 valeurs de même type (double).
Ils ont 3 valeurs dont on ne sait rien à ce niveau. On n'a pas besoin de
plus d'info vu qu'on veut juste les afficher.
Je veux juste une interface de manipulation commune. Le mot interface est
lâché...

La fonction qui lui donne les Triplets à afficher se charge de mettre
la légende à jour. En fait, mon graphe est un bête graphe générique
qui ne fait que dessiner des Triplet. Passer par Triplet me permet de
ne pas devoir le spécialiser pour chaque nouveau type de Triplet,
réduction du couplage, ...


Tout à fait d'accord. Mais je lui donnerai toujours des triplets dans
une unité préspécifiée.


Suite à ces très riches explications (merci beaucoup!), je ressors ton idée
d'un opérateur de convertion que je reyclerais volontier en interface. J'ai
un peu avancé du côté du graphique, et j'ai maintenant un objet Courbe.
Cette Courbe contient la liste des Triplets à dessiner, plus leur nom,
etc...
Le truc c'est donc de pourvoir créer une courbe Curve à partir d'une liste
de ABC ou une liste de DEF, etc...
Que penses-tu d'une interface implémentées par ABC, DEF, ... de ce style :

class TripletBase
{
public:
virtual ~TripletBase();
virtual std::vector<double> GetValues() const = 0;
};

Et la classe Curve :

class Curve
{
public:
void AddPoint( const std::vector<double> & V )
{
this->Points.push_back( V );
}
private:
std::vector< std::vector<double> > Points;
};

Le graphe est encore plus générique, il n'a plus aucun lien avec les
Triplets.
Pour chaque point <p> ABC ou DEF ou ... à voir, on l'ajoute à <c> via
c.Addpoint( p.GetValues() );

L'interface ne sert pas à grand chose, si ce n'est à faire penser qu'il faut
créer une fonction GetValues pour chaque type de Triplet... (est-ce justifié
?)
Merci beaucoup.

--
Aurélien REGAT-BARREL


Avatar
kanze
"Aurélien REGAT-BARREL" wrote in
message news:<40d857a7$0$10168$...

ABC, DEF, ... sont tous des Triplet. La seule chose qui les
différencie, c'est le contexte de leurs valeurs.


Toutes les opérations sur eux sont identique ?


La seule opération faite sur Triplet est l'affichage.


Alors, *conceptuellement*, je verais une interface (c-à-d une classe
abstraite sans état) Triplet, dont les autres classes héritent.

Aussi, si la fonctionnalité est réelement limitée à l'affichage, je
l'indiquerais dans le nom : TripletAffichable, ou quelque chose du
genre. Voire même plus -- ce qui caractèrise cette classe de base, c'est
bien l'« Affichable » ; si j'ai bien compris, elle n'a aucune autre
fonctionnalité d'un Triplet. (Mais un bon nom qui dit ça ne me vient pas
à l'esprit.) Éventuellement, on pourrait même imaginer une interface
plus abstraite : une SuiteAffichable, avec une fonction pour déterminer
le nombre d'éléments, et une autre pour accéder à chaque élément.

Ça, c'est conceptuellement, et ne tient pas compte des contraites de
l'implémentation, comme par exemple des problèmes éventuels liés à un
mélange entre le polymorphisme et une sémantique de valeur. Encore que
c'est fort possible que tu tombes dans un cas particulier où le problème
ne se pose pas. Si j'ai bien compris, cette interface ne servira que
pour l'affichage -- partout ailleurs dans le programme, il ne s'agit que
des ABC ou des DEF, jamais des Triplet. Or, dans la mésure que 1)
l'affichage n'influe en rien à la durée de vie des objets, et 2) on ne
manipule pas les objets par l'interface Triplet ailleurs que dans
l'affichage, on pourrait bien hériter d'une interface Triplet, et passer
un std::vector< Triplet* > à l'affichage. Ce genre de mélange ne me
plaît pas en général, mais il y a des cas particuliers où il se
justifie, où il est plus propre que les alternatifs.

(N'empêche que je considèrerais de très près l'opérateur de conversion.
En fin de compte, un ABC n'est pas un Triplet -- il a un Triplet qui
sert à l'affichage. Et ce qu'on affiche, c'est un Triplet. Point à la
ligne. Ce n'est pas une implémentation quelconque d'un Triplet. Enfin,
c'est un point de vue. Le problème, c'est qu'on peut régarder prèsque
tout de beaucoup de façons différentes. AMHA, les deux solutions
marchent, et avec le peu d'information que j'ai, l'une ou l'autre est
« assez bien ».)

ABC va être les coordonnées d'un point en mètres, DEF en yards,
GHI en kilomètres, etc...


D'accord. J'ai compris.

Est-ce que les valeurs dans le Triplet sont toujours dans la même
unité ? C-à-d : quand je crée une Cordonnée en yards, est-ce que
constructeur convertit les yards en mètres avant de les stocker, ou
est-ce qu'il stocke réelement des yards ?


Non. Pire, mon exemple est mauvais. Il faut plutot voir des mètres,
puis des degrés (angles), etc...


D'accord.

Je m'en doutais un peu. Je me posais cependant la question si on aurait
pu les unifier. Parce que quand on peut, ça peut simplifier la vie.

Mais vue de cet angle, est-ce que l'héritage a un sens ? Un Triplet,
c'est trois valeurs abstraites. Or, si j'ai bien compris, un ABC ne se
compose pas de trois valeurs abstraites, mais de trois distances, tandis
qu'un DEF se consiste en trois angles.

À mon avis, ça argue pour la solution avec ou bien un opérateur de
conversion, ou bien une fonction explicite : Triplet ABC::getValues(),
ou quelque chose du genre.

Le point commun est que ces triplets sont le reflet de certaines
propriétés, mais dans des unités qui n'ont rien à voir. Les Triplet ne
sont absolument pas comparables entre eux, les unités ne sont pas
convertibles (distances, angles, pourcentages, ...).


D'accord. Donc, les ABC et les DEF n'ont rien en commun, à part la
caractèristique prèsqu'accidentelle qu'ils ont trois quelque chose qui
se représente par une valeur numérique.

La question reste : qu'est-ce qui se passe si les types sont
mélangés ? Parce que si tu utilises le polymorphisme classique, on
peut les mélanger, et même s'ils ne sont jamais mélangés
aujourd'hui, ils le seront un jour ou l'autre.


C'est incohérent, ça n'a pas de sens, c'est à éviter. Je réalise que
la relation "est un" est juste en théorie, mais fausse en pratique.
Mes Triplets sont en fait des coordonnées 3D dans différents repères.
Donc ABC, DEF, ... sont bien des Triplets dans le sens ou ils ont 3
valeurs. Mais c'est juste ça le point commun : ils ont 3 valeurs de
même type (double). Ils ont 3 valeurs dont on ne sait rien à ce
niveau. On n'a pas besoin de plus d'info vu qu'on veut juste les
afficher. Je veux juste une interface de manipulation commune. Le mot
interface est lâché...


Dans ce cas, je pencherais pour une fonction explicite qui retourne un
Triplet. Avec éventuellement une fonction templatée pour construire le
tableau des Triplet, quelque chose du genre :

template< typename FwdIter >
std::vector< Triplet >
prepareLAffichage( FwdIter begin, FwdIter end )
{
std::vector< Triplet > result ;
while ( begin != end ) {
result.push_back( begin->getTriplet() ) ;
++ begin ;
}
return result ;
}

Note bien l'absence de toute classe de base des ABC, DEF, etc. C'est à
mon avis important, parce qu'on ne peut pas substituer un ABC pour un
DEF, même si on peut faire les même choses avec les deux. Or, le
template, c'est idéal pour enforcer les prédicats de type (c'est une
idée que je dois à Matt Austern) -- ici, l'utilisation du template
s'assure que tous les Triplets dans le tableau provient du même type.

La fonction qui lui donne les Triplets à afficher se charge de
mettre la légende à jour. En fait, mon graphe est un bête graphe
générique qui ne fait que dessiner des Triplet. Passer par Triplet
me permet de ne pas devoir le spécialiser pour chaque nouveau type
de Triplet, réduction du couplage, ...


Tout à fait d'accord. Mais je lui donnerai toujours des triplets
dans une unité préspécifiée.


Suite à ces très riches explications (merci beaucoup!), je ressors ton
idée d'un opérateur de convertion que je reyclerais volontier en
interface. J'ai un peu avancé du côté du graphique, et j'ai maintenant
un objet Courbe. Cette Courbe contient la liste des Triplets à
dessiner, plus leur nom, etc...

Le truc c'est donc de pourvoir créer une courbe Curve à partir d'une
liste de ABC ou une liste de DEF, etc... Que penses-tu d'une interface
implémentées par ABC, DEF, ... de ce style :

class TripletBase
{
public:
virtual ~TripletBase();
virtual std::vector<double> GetValues() const = 0;
};

Et la classe Curve :

class Curve
{
public:
void AddPoint( const std::vector<double> & V )
{
this->Points.push_back( V );
}
private:
std::vector< std::vector<double> > Points;
};

Le graphe est encore plus générique, il n'a plus aucun lien avec les
Triplets.
Pour chaque point <p> ABC ou DEF ou ... à voir, on l'ajoute à <c> via
c.Addpoint( p.GetValues() );

L'interface ne sert pas à grand chose, si ce n'est à faire penser
qu'il faut créer une fonction GetValues pour chaque type de Triplet...
(est-ce justifié ?)


Comme j'ai dit ci-dessus, d'après tes descriptions, j'ai l'impression
ici que c'est plutôt un rôle pour les templates. On ne peut pas
substituer un Triplet pour un autre librement. On a un prédicat sur le
typage, que tous les éléments soient en fait le même type. Enforcer les
prédicats du typage, c'est un rôle où les templates brillent. Or que le
but de l'héritage, en général, c'est justement de se libérer des
contraites du typage.

--
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
Aurélien REGAT-BARREL
La seule opération faite sur Triplet est l'affichage.


Alors, *conceptuellement*, je verais une interface (c-à-d une classe
abstraite sans état) Triplet, dont les autres classes héritent.

Aussi, si la fonctionnalité est réelement limitée à l'affichage, je
l'indiquerais dans le nom : TripletAffichable, ou quelque chose du
genre. Voire même plus -- ce qui caractèrise cette classe de base, c'est
bien l'« Affichable » ; si j'ai bien compris, elle n'a aucune autre
fonctionnalité d'un Triplet. (Mais un bon nom qui dit ça ne me vient pas
à l'esprit.) Éventuellement, on pourrait même imaginer une interface
plus abstraite : une SuiteAffichable, avec une fonction pour déterminer
le nombre d'éléments, et une autre pour accéder à chaque élément.


C'est ce que je voulais faire, une interface IPlottable ou... Moi aussi je
n'ai pas trouvé de nom :-)

Ça, c'est conceptuellement, et ne tient pas compte des contraites de
l'implémentation, comme par exemple des problèmes éventuels liés à un
mélange entre le polymorphisme et une sémantique de valeur. Encore que
c'est fort possible que tu tombes dans un cas particulier où le problème
ne se pose pas. Si j'ai bien compris, cette interface ne servira que
pour l'affichage -- partout ailleurs dans le programme, il ne s'agit que
des ABC ou des DEF, jamais des Triplet. Or, dans la mésure que 1)
l'affichage n'influe en rien à la durée de vie des objets, et 2) on ne
manipule pas les objets par l'interface Triplet ailleurs que dans
l'affichage, on pourrait bien hériter d'une interface Triplet, et passer
un std::vector< Triplet* > à l'affichage. Ce genre de mélange ne me
plaît pas en général, mais il y a des cas particuliers où il se
justifie, où il est plus propre que les alternatifs.


C'était mon souhait initial, jusqu'à ce que tu interviennes.

(N'empêche que je considèrerais de très près l'opérateur de conversion.
En fin de compte, un ABC n'est pas un Triplet -- il a un Triplet qui
sert à l'affichage. Et ce qu'on affiche, c'est un Triplet. Point à la
ligne. Ce n'est pas une implémentation quelconque d'un Triplet. Enfin,
c'est un point de vue. Le problème, c'est qu'on peut régarder prèsque
tout de beaucoup de façons différentes. AMHA, les deux solutions
marchent, et avec le peu d'information que j'ai, l'une ou l'autre est
« assez bien ».)


L'opérateur de conversion me semble maintenant meilleur. Un seul bémol :
comment faire penser à un programmeur futur qui reprend le projet qu'il doit
coder cet opérateur. D'où mon idée de conserver une interface héritée
commune, pour souligner cette info.

Est-ce que les valeurs dans le Triplet sont toujours dans la même
unité ? C-à-d : quand je crée une Cordonnée en yards, est-ce que
constructeur convertit les yards en mètres avant de les stocker, ou
est-ce qu'il stocke réelement des yards ?


Non. Pire, mon exemple est mauvais. Il faut plutot voir des mètres,
puis des degrés (angles), etc...


D'accord.

Je m'en doutais un peu. Je me posais cependant la question si on aurait
pu les unifier. Parce que quand on peut, ça peut simplifier la vie.

Mais vue de cet angle, est-ce que l'héritage a un sens ? Un Triplet,
c'est trois valeurs abstraites. Or, si j'ai bien compris, un ABC ne se
compose pas de trois valeurs abstraites, mais de trois distances, tandis
qu'un DEF se consiste en trois angles.


Le seul point commun c'est qu'il y a un "Triplet" initial : ABC, et que les
suivants sont construits à partir de celui-ci.
Mais on parle bien de ABC, et pas de "Triplet".
Le but de "Triplet" est simplement de pouvoir écrire une seule fonction
Affiche( const Triplet & T );
à laquelle je peux passer un ABC, un DEF, ou un autre...

À mon avis, ça argue pour la solution avec ou bien un opérateur de
conversion, ou bien une fonction explicite : Triplet ABC::getValues(),
ou quelque chose du genre.


Je suis aussi d'accord. Je pense maintenant à
Affiche( const std::vector<double> & V );
et soit un opérateur de conversion, soit une fonction GetValues(). Mais le
truc c'est que je veux une seule solution utilisée partout.
Pas de ABC::GetTriplet(), DEF::GetValues() et d'opérateur de conversion pour
HIJ. D'ou cette idée d'interface de base commune.

Dans ce cas, je pencherais pour une fonction explicite qui retourne un
Triplet. Avec éventuellement une fonction templatée pour construire le
tableau des Triplet, quelque chose du genre :

template< typename FwdIter >
std::vector< Triplet >
prepareLAffichage( FwdIter begin, FwdIter end )
{
std::vector< Triplet > result ;
while ( begin != end ) {
result.push_back( begin->getTriplet() ) ;
++ begin ;
}
return result ;
}

Note bien l'absence de toute classe de base des ABC, DEF, etc. C'est à
mon avis important, parce qu'on ne peut pas substituer un ABC pour un
DEF, même si on peut faire les même choses avec les deux. Or, le
template, c'est idéal pour enforcer les prédicats de type (c'est une
idée que je dois à Matt Austern) -- ici, l'utilisation du template
s'assure que tous les Triplets dans le tableau provient du même type.


Je m'apprêtais à dire dans une précédente réponse que l'idéal serait
d'interdire le mixage dans un même tableau de ABC et de DEF. Mais ça ne me
parraissait pas possible, car même avec une classe de base Triplet, on
pouvait pas interdire le gars de pusher un ABD.GetValues() puis un
DEF.GetValues().
Cette solution de template est terrible. Je suis peu habitué aux templates,
je n'ai pas le réflexe de les utiliser. Pourtant j'adore la généricité et la
STL. L'idéal aurait été une fonction générique Affiche() dans mon graphe,
juste cette fonction. Mais ce n'est pas possible. Je n'avais en fait pas
pensé à créer uen fonction template que j'utilise à l'intérieur de
Affiche().
Là il est toujours possible de mixer des ABC et DEF (en construisant 2
vector et les mélangeant), mais faut vraiment le vouloir. Je ne pensais pas
pouvoir arriver à un tel niveau de typage. C'est très bien.

L'interface ne sert pas à grand chose, si ce n'est à faire penser
qu'il faut créer une fonction GetValues pour chaque type de Triplet...
(est-ce justifié ?)


Comme j'ai dit ci-dessus, d'après tes descriptions, j'ai l'impression
ici que c'est plutôt un rôle pour les templates. On ne peut pas
substituer un Triplet pour un autre librement. On a un prédicat sur le
typage, que tous les éléments soient en fait le même type. Enforcer les
prédicats du typage, c'est un rôle où les templates brillent. Or que le
but de l'héritage, en général, c'est justement de se libérer des
contraites du typage.


Belle conclusion.
Un bémol demeure cependant, et je crois que c'est un défaut des templates
C++.
Comment indiquer qu'il faut coder une fonction getTriplet() ?

Je te remercie beaucoup pour tout ce temps consacré à ce problème. Je crois
qu'une solution élégante a été mise au point, bien plus élégante que mon
code initial en tous cas.
J'aimerais pouvoir te dire que je m'apprête à la coder, mais comme je dois
intégrer mon code dans du code existant et non plus l'inverse (code à base
de char * et de macros de 150 lignes chacune soit plus de 1000 lignes de
macros dans le but de fabriquer un std::string et un std::vector maisons) je
ne sais pas si je vais pouvoir débarquer avec mes classes à moi et mes
templates. C'est frustrant mais je ne suis que stagiaire (on me l'a bien
fait comprendre) et c'est pas moi qui décide... Snif.

--
Aurélien REGAT-BARREL



Avatar
kanze
"Aurélien REGAT-BARREL" wrote in
message news:<40d94ac9$0$4400$...

[...]
Ça, c'est conceptuellement, et ne tient pas compte des contraites de
l'implémentation, comme par exemple des problèmes éventuels liés à
un mélange entre le polymorphisme et une sémantique de
valeur. Encore que c'est fort possible que tu tombes dans un cas
particulier où le problème ne se pose pas. Si j'ai bien compris,
cette interface ne servira que pour l'affichage -- partout ailleurs
dans le programme, il ne s'agit que des ABC ou des DEF, jamais des
Triplet. Or, dans la mésure que 1) l'affichage n'influe en rien à la
durée de vie des objets, et 2) on ne manipule pas les objets par
l'interface Triplet ailleurs que dans l'affichage, on pourrait bien
hériter d'une interface Triplet, et passer un std::vector< Triplet*
à l'affichage. Ce genre de mélange ne me plaît pas en général,
mais il y a des cas particuliers où il se justifie, où il est plus

propre que les alternatifs.


C'était mon souhait initial, jusqu'à ce que tu interviennes.


C'est peut-être toujours la solution la plus simple. Il ne faut pas être
plus royaliste que le roi : la meilleur solution, c'est celle 1) qui
marche et 2) qu'un programmeur Dupont comprend. C'est souvent aussi une
qui correspond à un idiome consacré, mais l'idiome n'est jamais que le
moyen. Si exceptionellement on tombe sur une solution qui « viole » un
peu les règles consacrée, mais qui marche et qui est facile à
comprendre, elle est bonne, nonobstant la violation de la règle. (Mais
il ne faut pas en abuser. Si des idiomes deviennent consacrés, c'est
souvent parce qu'il marche et qu'il sont facile à comprednre.)

(N'empêche que je considèrerais de très près l'opérateur de
conversion. En fin de compte, un ABC n'est pas un Triplet -- il a
un Triplet qui sert à l'affichage. Et ce qu'on affiche, c'est un
Triplet. Point à la ligne. Ce n'est pas une implémentation
quelconque d'un Triplet. Enfin, c'est un point de vue. Le problème,
c'est qu'on peut régarder prèsque tout de beaucoup de façons
différentes. AMHA, les deux solutions marchent, et avec le peu
d'information que j'ai, l'une ou l'autre est « assez bien ».)


L'opérateur de conversion me semble maintenant meilleur. Un seul
bémol : comment faire penser à un programmeur futur qui reprend le
projet qu'il doit coder cet opérateur. D'où mon idée de conserver une
interface héritée commune, pour souligner cette info.


Qu'il oublisse, et qu'il essaie d'instantier le template, il s'en
apercevra.:-)

Évidemment, le message d'erreur du compilateur ne serait pas forcement
des plus clairs. Mais enfin, ça doit être une réaction automatique quand
on a des erreurs dans l'instantiation des templates, que de regarder la
documentation pour se renseigner sur les contraites sur des paramètres
d'instantiation.

À cet égard, c'est probable qu'une fonction nommée donnerait un message
d'erreur un peu plus clair. Avec la conversion automatique, s'il manque,
il y aurait un message du genre de « type mismatch », qui n'est pas
forcement très parlant. Avec la fonction, le message serait du genre,
« cannot find getTriplet » ou « no function getTriplet in XXX ».

Est-ce que les valeurs dans le Triplet sont toujours dans la
même unité ? C-à-d : quand je crée une Cordonnée en yards,
est-ce que constructeur convertit les yards en mètres avant de
les stocker, ou est-ce qu'il stocke réelement des yards ?


Non. Pire, mon exemple est mauvais. Il faut plutot voir des
mètres, puis des degrés (angles), etc...


D'accord.

Je m'en doutais un peu. Je me posais cependant la question si on aurait
pu les unifier. Parce que quand on peut, ça peut simplifier la vie.

Mais vue de cet angle, est-ce que l'héritage a un sens ? Un Triplet,
c'est trois valeurs abstraites. Or, si j'ai bien compris, un ABC ne se
compose pas de trois valeurs abstraites, mais de trois distances, tandis
qu'un DEF se consiste en trois angles.


Le seul point commun c'est qu'il y a un "Triplet" initial : ABC, et
que les suivants sont construits à partir de celui-ci. Mais on parle
bien de ABC, et pas de "Triplet". Le but de "Triplet" est simplement
de pouvoir écrire une seule fonction
Affiche( const Triplet & T );
à laquelle je peux passer un ABC, un DEF, ou un autre...


Ce qui argue très fort pour un type à part. Est-ce qu'on peut exclure
d'office qu'il n'y aurait jamais de type qui s'affiche de deux façons :
ou comme un Triplet, ou d'une autre façon ?

À mon avis, ça argue pour la solution avec ou bien un opérateur de
conversion, ou bien une fonction explicite : Triplet
ABC::getValues(), ou quelque chose du genre.


Je suis aussi d'accord. Je pense maintenant à
Affiche( const std::vector<double> & V );
et soit un opérateur de conversion, soit une fonction
GetValues(). Mais le truc c'est que je veux une seule solution
utilisée partout. Pas de ABC::GetTriplet(), DEF::GetValues() et
d'opérateur de conversion pour HIJ. D'ou cette idée d'interface de
base commune.


Si tu utilise une fonction templatée comme passerelle, ou bien la
fonction a le même protocol partout, ou bien il y a une erreur à
l'instantiation du template.

[...]
L'interface ne sert pas à grand chose, si ce n'est à faire penser
qu'il faut créer une fonction GetValues pour chaque type de
Triplet... (est-ce justifié ?)


Comme j'ai dit ci-dessus, d'après tes descriptions, j'ai
l'impression ici que c'est plutôt un rôle pour les templates. On ne
peut pas substituer un Triplet pour un autre librement. On a un
prédicat sur le typage, que tous les éléments soient en fait le même
type. Enforcer les prédicats du typage, c'est un rôle où les
templates brillent. Or que le but de l'héritage, en général, c'est
justement de se libérer des contraites du typage.


Belle conclusion.
Un bémol demeure cependant, et je crois que c'est un défaut des
templates C++.
Comment indiquer qu'il faut coder une fonction getTriplet() ?


Il existe des récherches sur le problème de spécifier des contraintes
des templates. Et des solutions sans modification du langage, au moyen
de la méta-programmation. Dans ton cas, je crois que ça serait plus
qu'il en faut. Si le développeur a oublié cette fonction, il y aurait
une erreur lors de l'instantiation du template. Évidemment, l'erreur
serait dans le template, ce qui n'en facilite pas la compréhension pour
l'utilisateur. Mais dans ce cas-ci, le message d'erreur va bien parler
d'une manque d'une fonction getTriplet ; il citerait probablement aussi
le nom de la classe où le compilateur s'attend le trouver, et la plupart
des compilateurs vont aussi indiquer le numéro de la ligne qui a
déclencher l'instantiation. Si avec tout ça, et une documentation des
contraints sur le template qui en parle, le développeur n'arrive pas à
comprendre ce qu'il doit faire...

Je te remercie beaucoup pour tout ce temps consacré à ce problème. Je
crois qu'une solution élégante a été mise au point, bien plus élégante
que mon code initial en tous cas.

J'aimerais pouvoir te dire que je m'apprête à la coder, mais comme je
dois intégrer mon code dans du code existant et non plus l'inverse
(code à base de char * et de macros de 150 lignes chacune soit plus de
1000 lignes de macros dans le but de fabriquer un std::string et un
std::vector maisons) je ne sais pas si je vais pouvoir débarquer avec
mes classes à moi et mes templates. C'est frustrant mais je ne suis
que stagiaire (on me l'a bien fait comprendre) et c'est pas moi qui
décide... Snif.


Bien venu au monde professionnel:-). En trente ans d'expérience, j'ai pu
implémenter un programme nouveau de rien deux fois. Sinon, c'était
toujours adapter quelque chose d'existant. Dans la réalité, au moins
dans l'industrie (je ne sais pas comment ça se passe dans les écoles),
on est toujours confronté des contraints contradictoires : il faut faire
beau, avec un existant, dans un delai pas toujours très réaliste. Il
faut toujours faire un choix : qu'est-ce qu'il faut faire, qu'est-ce que
je peux faire de plus du minimum, et qu'est-ce qu'il faut laisser du
côté, au moins pour l'instant -- la perfection n'est pas de ce monde.
Savoir faire ce choix, c'est souvent plus important que bien connaître
des templates ou la conception OO.

Mais ne laisse pas ce fait te dégouter de l'informatique, ni du monde
industriel ou commerciel. Il faut partir du principe que le programme
que tu laisses ne serait jamais parfait. Mais avec un peu d'effort, tu
peux faire en sort qu'il serait toujours mieux que quand tu l'as pris en
main. Ce qui est aussi une satisfaction. (Personnellement, je tire aussi
une très grande satisfaction du fait que certains programmes sur
lesquels j'ai travaillé sont réelement utilisés, de façon intense. Faire
un programme parfait, mais qui ne sert pas, ne me donnerait pas autant
de satisfaction.)

--
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
Aurélien REGAT-BARREL
Le seul point commun c'est qu'il y a un "Triplet" initial : ABC, et
que les suivants sont construits à partir de celui-ci. Mais on parle
bien de ABC, et pas de "Triplet". Le but de "Triplet" est simplement
de pouvoir écrire une seule fonction
Affiche( const Triplet & T );
à laquelle je peux passer un ABC, un DEF, ou un autre...


Ce qui argue très fort pour un type à part. Est-ce qu'on peut exclure
d'office qu'il n'y aurait jamais de type qui s'affiche de deux façons :
ou comme un Triplet, ou d'une autre façon ?


En fait j'ai plusieurs graphes. Pour l'instant 2 sont limités à un seul type
de Triplet, et un autre est capable de presque tous les représenter, à 1
exception près, pour lequel ça n'a pas de sens.
Donc je compte créer une classe Curve pouvant être construite à partir de
n'importe quelle liste de Triplet, et qui contient le nom du type de Triplet
utilisé pour la construire. Chaque graphe possède une méthode draw(const
Curve &) et libre à lui d'accepter ou non de dessiner cette courbe en
fonction de ton type.
Ces graphes dérivent de GraphBase qui accepte sans broncher une courbe Curve
à dessiner.

Le graphe qui dessine n'importe quel Triplet sauf ABC ressemblera donc à
(c'est un exemple minimal):

struct Curve
{
public:
std::vector<Triplet> Triplets; // les valeurs à dessiner
std::string Type; // type de Triplet : utilisé pour les legendes du
graph & les vérifications
};

class Graph : public GraphBase
{
void Graph::Draw( const Curve & C )
{
if ( C.Type == "ABC" ) throw InvalidCurveType;
GraphBase::DrawCurve( C.Triplets );
GraphBase::SetLegend( C.Type );
}
};

Ca évite 36 fonctions de dessin spécialisées pour chaque type de Triplet,
sans compter qu'il faut créer autant de type Curve qu'il y a de Triplet.
Reste à construire une Curve à partir d'une liste de Triplets pour les 36
types des Triplets...
J'ai pensé à plusieurs solutions (36 constructeurs dans Curve, ton template
utilisant une fonction template GetTripletType spécialisée 36 fois,...).
Voici celle que j'ai retenu car me paraissant le plus propre (couplage le
plus faible, code le plus restreint ) :

class ABC
{
public:
Triplet getTriplet() const;
static const std::string TypeName;
};

const std::string ABC::TypeName = "ABC";

template< typename T >
Curve
BuildCurve( const std::vector<T> & Values )
{
Curve curve;
for ( size_t i = 0, size = Values.size(); i < size; ++i )
{
curve.Triplets.push_back( Values[ i ].getTriplet() ) ;
}
curve.Type = T::TypeName;
return curve ;
}

void test()
{
std::vector<ABC> values;
Curve c = BuildCurve<ABC>( values );
}

C'est directement inspiré de ta solution, sauf que au lieu d'une fonction
getTriplet() il faut aussi le nom TypeName;

J'aimerais pouvoir te dire que je m'apprête à la coder, mais comme je
dois intégrer mon code dans du code existant et non plus l'inverse
(code à base de char * et de macros de 150 lignes chacune soit plus de
1000 lignes de macros dans le but de fabriquer un std::string et un
std::vector maisons) je ne sais pas si je vais pouvoir débarquer avec
mes classes à moi et mes templates. C'est frustrant mais je ne suis
que stagiaire (on me l'a bien fait comprendre) et c'est pas moi qui
décide... Snif.


Bien venu au monde professionnel:-). En trente ans d'expérience, j'ai pu
implémenter un programme nouveau de rien deux fois. Sinon, c'était
toujours adapter quelque chose d'existant. Dans la réalité, au moins
dans l'industrie (je ne sais pas comment ça se passe dans les écoles),


Je suis en entreprise.

on est toujours confronté des contraints contradictoires : il faut faire
beau, avec un existant, dans un delai pas toujours très réaliste. Il
faut toujours faire un choix : qu'est-ce qu'il faut faire, qu'est-ce que
je peux faire de plus du minimum, et qu'est-ce qu'il faut laisser du
côté, au moins pour l'instant -- la perfection n'est pas de ce monde.
Savoir faire ce choix, c'est souvent plus important que bien connaître
des templates ou la conception OO.


C'est pour gagner du temps que je reprend ce code qui ne me plaît guère.
Je me force un peu car j'ai entendu parler du syndrome NIH (Not Implemented
Here) qui est une des causes principales des retards et surcoûts de projets
(c'est pas moi qui l'ait fait donc c'est nul donc je refait).
Ce n'est pas reprendre du code qui me gêne, c'est reprendre du code bourré
de mégas macros difficiles à lires et très dures à débogguer.

Mais ne laisse pas ce fait te dégouter de l'informatique, ni du monde
industriel ou commerciel. Il faut partir du principe que le programme
que tu laisses ne serait jamais parfait. Mais avec un peu d'effort, tu
peux faire en sort qu'il serait toujours mieux que quand tu l'as pris en
main. Ce qui est aussi une satisfaction. (Personnellement, je tire aussi
une très grande satisfaction du fait que certains programmes sur
lesquels j'ai travaillé sont réelement utilisés, de façon intense. Faire
un programme parfait, mais qui ne sert pas, ne me donnerait pas autant
de satisfaction.)


J'essaye de travailler dans ce sens : améliorer les choses. Mais certains
pensent que c'est déjà parfait.
Mais je suis optimiste, je devrait pouvoir utiliser cette approche en créant
une petite couche de liaison entre le code bourré de macros et un code
"Thanks to James Kanze for this great tip". Intégrer du code bourré de
macros ne me gêne pas trop s'il marche. Ce qui me désespérais c'est de
devoir y intégrer mon code et donc d'utiliser cette programmation par
macros...Je pense arriver à y échapper. :-)

--
Aurélien REGAT-BARREL


Avatar
Christophe de VIENNE
Aurélien REGAT-BARREL wrote:
<snip>
J'ai pensé à plusieurs solutions (36 constructeurs dans Curve, ton template
utilisant une fonction template GetTripletType spécialisée 36 fois,...).
Voici celle que j'ai retenu car me paraissant le plus propre (couplage le
plus faible, code le plus restreint ) :

class ABC
{
public:
Triplet getTriplet() const;
static const std::string TypeName;
};

const std::string ABC::TypeName = "ABC";

template< typename T >
Curve
BuildCurve( const std::vector<T> & Values )
{
Curve curve;
for ( size_t i = 0, size = Values.size(); i < size; ++i )
{
curve.Triplets.push_back( Values[ i ].getTriplet() ) ;
}
curve.Type = T::TypeName;
return curve ;
}


Tu peux faire encore un peu plus générique comme ça :

template< typename InputIterator >
Curve
BuildCurve( InputIterator first, InputIterator last )
{
Curve curve;
for( InputIterator i = first; i != last; ++i )
{
curve.Triplets.push_back( i->getTriplet() );
}
curve.Type = iterator_traits<InputIterator>::value_type::TypeName;
return curve;
}


void test()
{
std::vector<ABC> values;
Curve c = BuildCurve<ABC>( values );


Curve c = BuildCurve( values.begin(), values.end() );

}




L'avantage est que tu peux construire un object Curve à partir de
n'importe quel type de conteneur.
Cela dit si tu peux modifier Curve (j'ai pas lu l'intégralité du fil
dont j'ai un doute) tu peux faire la même chose dans un constructeur
template :

class Curve {
public:
template< typename InputIterator >
Curve(InputIterator first, InputIterator last );
}


Ce qui donne, pour l'instanciation :

Curve c( values.begin(), values.end() );


A+

Christophe

--
Christophe de Vienne

Avatar
Aurélien REGAT-BARREL
Tu peux faire encore un peu plus générique comme ça :

template< typename InputIterator >
Curve
BuildCurve( InputIterator first, InputIterator last )
{
Curve curve;
for( InputIterator i = first; i != last; ++i )
{
curve.Triplets.push_back( i->getTriplet() );
}
curve.Type = iterator_traits<InputIterator>::value_type::TypeName;
return curve;
}


Je connaissais pas, je pensais que c'était possible avec le RTTI, mais pas
avec la STL. Mais je ne peux pas utiliser ta solution car le nom de mes
classes n'est pas toujours parfaitement égal à celui du Triplet (Par exemple
le triplet TempDeg s'appelle "Temp°", ...). Mais merci pour le tuyau.

L'avantage est que tu peux construire un object Curve à partir de
n'importe quel type de conteneur.
Cela dit si tu peux modifier Curve (j'ai pas lu l'intégralité du fil
dont j'ai un doute) tu peux faire la même chose dans un constructeur
template :

class Curve {
public:
template< typename InputIterator >
Curve(InputIterator first, InputIterator last );
}


Ce qui donne, pour l'instanciation :

Curve c( values.begin(), values.end() );


Ah ben décidémment je pensais pas qu'on pouvait mettre une fonction template
comme membre d'une classe "normale". Je croyais qu'on pouvais seulement
faire des fonctions globales. Comment ça se passe après au niveau de la
compilation ? Il génère autant de fonctions membres que t'instancies cette
fonction template ?
Mais je vais m'en tenir à une fonction séparée car je risque plus tard de
devoir à nouveau convertir mes Triplets, donc avoir besoin du même code que
dans le constructeur et donc le dupliquer. En revanche ajouter un
constructeur template qui utilise la fonction template de conversion, c'est
pile poil ce qu'il me faut. C'est terrible, pouvoir accepter un argument
générique dans une classe non template est vraiment ce que je voulais depuis
le début.
Merci pour ces astuces. Il était temps que tu Vienne :-)
Encore merci à James aussi pour ce véritable cours particulier et ses
retours d'expérience.
A+

--
Aurélien REGAT-BARREL
Les templates c'est bon mangez en!

Avatar
Christophe de VIENNE
Aurélien REGAT-BARREL wrote:
Tu peux faire encore un peu plus générique comme ça :

template< typename InputIterator >
Curve
BuildCurve( InputIterator first, InputIterator last )
{
Curve curve;
for( InputIterator i = first; i != last; ++i )
{
curve.Triplets.push_back( i->getTriplet() );
}
curve.Type = iterator_traits<InputIterator>::value_type::TypeName;
return curve;
}


Je connaissais pas, je pensais que c'était possible avec le RTTI, mais pas
avec la STL. Mais je ne peux pas utiliser ta solution car le nom de mes
classes n'est pas toujours parfaitement égal à celui du Triplet (Par exemple
le triplet TempDeg s'appelle "Temp°", ...). Mais merci pour le tuyau.



Heu, pas compris le problème là. Ma solution n'est pas plus
contraignante que la fonction que tu as proposé, elle est juste plus
souple car tu tout conteneur avec des itérateurs fera l'affaire, même un
tableau "à la C" si ça te chante. C'est d'ailleur inspiré de ce qui est
fait pour certaines fonctions des conteneurs de la STL (cf
http://www.sgi.com/tech/stl/Vector.html par exemple).

<snip constructeur template>
Ah ben décidémment je pensais pas qu'on pouvait mettre une fonction template
comme membre d'une classe "normale". Je croyais qu'on pouvais seulement
faire des fonctions globales. Comment ça se passe après au niveau de la
compilation ? Il génère autant de fonctions membres que t'instancies cette
fonction template ?


C'est à peu près ça.

Mais je vais m'en tenir à une fonction séparée car je risque plus tard de
devoir à nouveau convertir mes Triplets, donc avoir besoin du même code que
dans le constructeur et donc le dupliquer. En revanche ajouter un
constructeur template qui utilise la fonction template de conversion, c'est
pile poil ce qu'il me faut. C'est terrible, pouvoir accepter un argument
générique dans une classe non template est vraiment ce que je voulais depuis
le début.


Il y a encore une option, mais qui te fait un peu changer tes classes,
c'est de définir l'operateur de conversion vers Triplet. Comme ça tu
peux remplir ton std::vector directement, ce qui donne quelque chose comme :

class ABC {
// ...
public:
operator Triplet () const { return Triplet(a, b, c); }
};

class Curve {
private:
std::vector<Triplet> m_triplets;
public:
template<InputIterator>
Curve(InputIterator first, InputIterator last):
m_triplets(first, last)
{
}
}


// ...

int main() {
std::list<ABC> l; // n'importe quel conteneur fait l'affaire
// remplir l
Curve c(l.begin(), l.end());
}


C'est encore plus concis comme écriture, et donc moins explicite. C'est
à toi de voir ce que tu préfères.

A+

Christophe

--
Christophe de Vienne


Avatar
Falk Tannhäuser
"Aurélien REGAT-BARREL" wrote:

Ah ben décidémment je pensais pas qu'on pouvait mettre une fonction template
comme membre d'une classe "normale". Je croyais qu'on pouvais seulement
faire des fonctions globales. Comment ça se passe après au niveau de la
compilation ? Il génère autant de fonctions membres que t'instancies cette
fonction template ?


Oui, tout à fait. La seule restriction c'est que le membre template ne
peut pas être virtuel.

Falk

1 2 3 4 5