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:<40dae2c6$0$4556$...

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 );
}
};


En ce qui concerne le typage, est-ce que tu connais dynamic_cast et
typeid ? Pour le premier emploi, au moins, on pourrait très bien se
servir de typeid et std::type_info, et libérer l'utilisateur du devoir
de le déclarer lui-même.

Mais ici aussi, j'aurais peut-être une approche un peu différente. Tu as
de différents types de Triplet, et de différents types de Graph. Alors,
on pourrait imaginer que pour chaque type de Triplet, on associe un
std::set< std::type_info const* >, avec les types des graphes auquels le
Triplet peut être associé. Voire...

On définit une classe ProprietesDeTriplet, qui contient comme membres
l'ensemble des graphes supportés, le libellé pour la graphe, etc. Avec
une instance par type de Triplet ; Curve contiendrait alors un pointeur
à cet objet, et chaque type de Graph pourrait commencer par :

if ( c.tripletInfo->supportedGraphTypes.find( &typeid( *this ) )
== c.tripletInfo->supportedGraphTypes.end() ) {
throw InvalidCurveType ;
}

// ...

setLegent( c.tripletInfo.libelle ) ;

Mais ça met l'information sur les types de graphes supportés dans les
classes ABC, DEF, etc. Je ne sais pas assez de l'application pour savoir
si c'est bien ou non. Si l'information doit être dans les Graph, on peut
l'y mettre aussi. Ou on pourrait se servir d'un map indépendant des
deux.

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 ;
}


J'aurais tendance à en faire une fonction membre statique. Voire
carrément de mettre le code dans un constructeur templaté. À condition,
évidemment, d'avoir un compilateur à peu près à jour, qui supporte des
membres templatés (mais ça devient de moins en moins un problème).

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.


Je m'en doutais. Si tu as à faire avec du code existant.

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.


Tout à fait. Le but n'est pas de faire parfait, mais de faire
acceptable, à un prix acceptable.

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.


C'est toujours un compromis. Combien coûte reprendre de tel code, en
termes de maintienabilité, par exemple, par rapport à combien on
économise en le réprenant. Si c'est vrai que souvent, on ne se base pas
sur un existant à cause dy syndrôme NIH, parfois, aussi, il faut dire
qu'investir le temps d'apprendre assez pour utiliser l'existant coûte
plus cher que de faire du neuf. Neuf qui serait évidemment éminemment
réutilisable, par ce que c'est nous qui le faisons, et que donc, c'est
proprement écrit, bien documenté... :-)

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.


Évidemment, puisque c'est eux qui l'ont fait:-).

L'importance, c'est de se rendre compte que ce n'est jamais parfait.
Même quand on l'a fait soi-même. Il y a toujours des contraits externes,
de temps ou d'autres choses, qui font qu'on aurait pu faire mieux, si.

L'importance, aussi, c'est de se rendre compte que nous ne connaissons
jamais les conditions et les contraintes qui étaient présentes quand le
code était écrit. On ne peut donc jamais dire qu'on aurait fait mieux.
Tout code est ce qu'il est. Il a une histoire, qu'on ne connaît jamais
parfaitement, mais qui explique pourquoi il est ce qu'il est.
L'importance, ce n'est pas comment il y est arrivé ; c'est où on va de
là. Il faut savoir accepter ce qu'ont fait les autres. Ce n'est pas
« leur code, c'est de la merde », mais « s'ils n'avaient pas eu
tellement de contraites externes, ils l'auraient sûrement écrit
différemment. »

Autrement dit, une fois qu'il existe, le code n'est ni bon, ni mauvais.
Il est, c'est tout.

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. :-)


C'est une bonne approche. On utilise ce qu'on peut. S'il y a des parties
vraiment sales, on les isole. L'importance, c'est le résultat. Le
résultat total : que ça marche, que c'est maintienable, et que ça n'a
pas coûté plus que 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
kanze
Christophe de VIENNE wrote in message
news:<newscache$kwhtzh$hqc$...
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;
}


Je ne dis pas non, mais n'oublie pas qu'ici, la généricité n'est pas le
but principale. Le but, surtout, c'est l'enforcement par le compilateur
des prédicats sur le typage. BuildCurve n'est pas une fonction générique
qui va servir dans des applications non encore connues ; elle fait
partie de l'application, et si dans l'application, tous les ABC et al.
se trouve dans les vectors, pourquoi s'emmerder.

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.


Ce qui n'est vraiment un avantage que si on a plusieurs types de
conteneur. Ou si on ne connaît pas le type du conteneur.

Ici, le seul vrai avantage que je vois avec ta solution, c'est que c'est
idiomatique. Ce qui n'est pas negligeable comme avantage, mais qui est à
juger dans la contexte.

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 );
}


C'est tout à fait ce que je préconcise. J'imagine qu'il ne connais pas
la possibilité des constructeurs templatés. Mais peut-être simplement
qu'il a à faire avec un ancien compilateur, qui ne supporte pas les
templates membres.

Ce qui donne, pour l'instanciation :

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


Tout à fait. Et qui, aussi, permet à rendre les membres de données de
Curve privés. Qui améliore du coup l'encapsulation. Je dirais que cette
possibilité, si elle existe, est plus important que l'utilisation des
itérateurs (qui est bien « en principe », mais qui n'apporte pas
forcément beaucoup dans ce cas précis).

--
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
En ce qui concerne le typage, est-ce que tu connais dynamic_cast et
typeid ? Pour le premier emploi, au moins, on pourrait très bien se
servir de typeid et std::type_info, et libérer l'utilisateur du devoir
de le déclarer lui-même.


Je connais les 2, mais je ne m'en suis pratiquement jamais servi ailleurs
que dans des tests persos (surtout pout type_info).
J'avais effectivement pensé à utiliser le nom de la classe via RTTI. Mais
j'y ai renoncé car ce nom est parfois différent du nom de la classe. Par
exemple, la classe TempDeg correspond au Triplet "Temp°" (température en
degrés).

Mais ici aussi, j'aurais peut-être une approche un peu différente. Tu as
de différents types de Triplet, et de différents types de Graph. Alors,
on pourrait imaginer que pour chaque type de Triplet, on associe un
std::set< std::type_info const* >, avec les types des graphes auquels le
Triplet peut être associé. Voire...

On définit une classe ProprietesDeTriplet, qui contient comme membres
l'ensemble des graphes supportés, le libellé pour la graphe, etc. Avec
une instance par type de Triplet ; Curve contiendrait alors un pointeur
à cet objet, et chaque type de Graph pourrait commencer par :

if ( c.tripletInfo->supportedGraphTypes.find( &typeid( *this ) )
== c.tripletInfo->supportedGraphTypes.end() ) {
throw InvalidCurveType ;
}

// ...

setLegent( c.tripletInfo.libelle ) ;

Mais ça met l'information sur les types de graphes supportés dans les
classes ABC, DEF, etc. Je ne sais pas assez de l'application pour savoir
si c'est bien ou non. Si l'information doit être dans les Graph, on peut
l'y mettre aussi. Ou on pourrait se servir d'un map indépendant des
deux.


Après réflexion j'avais opté pour que ce soient les graphes qui disent ce
qu'ils savent représenter, et pas les Triplets qui autorient les graphes à
les représenter.
Utiliser typeid est intéressant, mais j'ai peur que cela fasse double emploi
avec un
static string Triplet::TypeName
qui est obligatoire car le nom peut différer de la classe. C'est plus
robuste qu'une simple comparaison de string, mais c'est peut être un peu
complexe / démesuré pour mon cas, et ça augmente le couplage.
Si je récapitule :
- le lien entre les graphes et les Triplets est fait par la classe Curve
- cette classe est construite par une fonction template à partir de
n'importe quelle liste de Triplets.
Si demain je dois représenter une courbe de plus qui n'est pas issue de
Triplets, c'est facilement faisable car Curve n'a absolument pas
connaissance des Triplets. Le typage par une chaine de caractère me parrait
plus souple.
L'esprit de cette approche n'est pas :
- dessiner la courbe représentant la liste de Triplets ABC, mais
- dessiner la courbe "ABC" construite à partir d'une liste de Triplets ABC
Ainsi les graphes disent juste "moi je sais dessiner la courbe "ABC", peu
importe qu'elle soit construite depuis une liste de Triplets ABC ou lue
depuis un fichier.

J'aurais tendance à en faire une fonction membre statique. Voire
carrément de mettre le code dans un constructeur templaté. À condition,
évidemment, d'avoir un compilateur à peu près à jour, qui supporte des
membres templatés (mais ça devient de moins en moins un problème).


Oui, c'est ce que m'a proposé Christophe, et ce que j'ai découvert au
passage (fonction membre template dans une classe qui ne l'est pas).

J'essaye de travailler dans ce sens : améliorer les choses. Mais
certains pensent que c'est déjà parfait.


Évidemment, puisque c'est eux qui l'ont fait:-).

L'importance, c'est de se rendre compte que ce n'est jamais parfait.
Même quand on l'a fait soi-même. Il y a toujours des contraits externes,
de temps ou d'autres choses, qui font qu'on aurait pu faire mieux, si.

L'importance, aussi, c'est de se rendre compte que nous ne connaissons
jamais les conditions et les contraintes qui étaient présentes quand le
code était écrit. On ne peut donc jamais dire qu'on aurait fait mieux.
Tout code est ce qu'il est. Il a une histoire, qu'on ne connaît jamais
parfaitement, mais qui explique pourquoi il est ce qu'il est.
L'importance, ce n'est pas comment il y est arrivé ; c'est où on va de
là. Il faut savoir accepter ce qu'ont fait les autres. Ce n'est pas
« leur code, c'est de la merde », mais « s'ils n'avaient pas eu
tellement de contraites externes, ils l'auraient sûrement écrit
différemment. »


Ce code a été développé par quelqu'un qui utilise ses libs persos qui
doivent être issues de 20 ans de développement.
Le problème c'est que ces "snippets" n'ont pas trop été revus en 20 ans.
Template, STL, std::string, membres const, ...rien de tout cela (mais
malloc/free, char * de taille fixe, macros à gogo).
C'est ce qui me frustre, que ça n'ait pas été mis au goût du jour. Je pense
que le principal boulot d'un informaticien est de se tenir à jour. A quoi ça
sert de faire évoluer la norme sinon ?

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. :-)


C'est une bonne approche. On utilise ce qu'on peut. S'il y a des parties
vraiment sales, on les isole. L'importance, c'est le résultat. Le
résultat total : que ça marche, que c'est maintienable, et que ça n'a
pas coûté plus que nécessaire.

Autrement dit, une fois qu'il existe, le code n'est ni bon, ni mauvais.
Il est, c'est tout.


De toutes façons, à terme (dans un petit moment quand même), que ce soit mon
code ou celui existant, ça dégagera et ce sera remplacé par une autre
solution.

--
Aurélien REGAT-BARREL


Avatar
Aurélien REGAT-BARREL
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).


Je me suis trompé. Je croyais que la STL permettait de récupérer le nom du
type "wrappé".
Ca me paraissait rudement fort. En fait le
iterator_traits<InputIterator>::value_type::TypeName était adapté de mon
exemple :-)
Donc c'est pas possible de l'utiliser sur n'importe quel objet vu qu'il faut
TypeName. Ce n'est d'ailleurs pas le but, car ce template est ce qui unit
les différents types de Triplet, donc je souhaite qu'il ne marche que pour
eux (ils doivent implémenter GetTriplet() ainsi que TypeName).

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
:

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


Oui James l'avait évoqué. Je n'ai pas encore tranché, mais je suis plutot
pour la fonction membre et non l'opérateur. En cas d'erreur dans le
template, je pense qu'elle sera plus facile à comprendre, et surtout ce
n'est pas trop le but des Triplets que d'être convertis implicitements.
Merci.

--
Aurélien REGAT-BARREL


Avatar
James Kanze
Christophe de VIENNE writes:

|> 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).

Oui et non. Je ne comprends pas trop son commentaire au sujet du nom du
type ; ton code marche parfaitement indépendamment du nom. En revanche,
il y a une contrainte quand au fait que c'est un constructeur -- tu ne
peux pas donner le nom que tu veux au constructeur, et du coup, tu ne
pourrais pas avoir deux constructeurs à partir des InputIterator qui
les traitent différemment.

|> <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.

Sans oublier la limitation que des fonctions templatées membre ne
peuvent pas être virtuelles. (Mais ce n'est pas une restriction nouvelle
en ce qui concerne le constructeur, comme c'est le cas ici:-).)

|> > 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.

Je ne veux pas t'y lancer trop (une chose à la fois), mais je parie
qu'il y a des choses à faire avec std::copy, std::back_inserter et
std::bind (ou peut-être des adaptateurs d'itérateurs de Boost). Bien
que... chaque fois que j'ai essayé, je suis arrivé à faire tout sauf ce
qu'il me fallait, et le résultat était complètement illisible. (En
revanche, c'est vraiment la solution « in ».)

|> 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)
|> {
|> }
|> }

On en a parlé. Ce qui me déplaît un peu dans cette solution, c'est que
le message d'erreur si l'utilisateur oublie l'opérateur de conversion va
être à peu près incompréhensible. Tandis que « cannot find match for
function getTripet in class ABC », c'est quand même assez suggestif.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34
Avatar
James Kanze
"Aurélien REGAT-BARREL" writes:

|> > En ce qui concerne le typage, est-ce que tu connais dynamic_cast
|> > et typeid ? Pour le premier emploi, au moins, on pourrait très
|> > bien se servir de typeid et std::type_info, et libérer
|> > l'utilisateur du devoir de le déclarer lui-même.

|> Je connais les 2, mais je ne m'en suis pratiquement jamais servi
|> ailleurs que dans des tests persos (surtout pout type_info). J'avais
|> effectivement pensé à utiliser le nom de la classe via RTTI. Mais
|> j'y ai renoncé car ce nom est parfois différent du nom de la classe.
|> Par exemple, la classe TempDeg correspond au Triplet "Temp°"
|> (température en degrés).

std::type_info::name() n'est pas très intéressant. Le résultat exacte
dépend de l'implémentation, et il y a des implémentations qui se sont
carrément moquées de l'utilisateur, au point de simplement renvoyer le
nom manglé.

|> > Mais ici aussi, j'aurais peut-être une approche un peu différente.
|> > Tu as de différents types de Triplet, et de différents types de
|> > Graph. Alors, on pourrait imaginer que pour chaque type de
|> > Triplet, on associe un std::set< std::type_info const* >, avec les
|> > types des graphes auquels le Triplet peut être associé. Voire...

|> > On définit une classe ProprietesDeTriplet, qui contient comme
|> > membres l'ensemble des graphes supportés, le libellé pour la
|> > graphe, etc. Avec une instance par type de Triplet ; Curve
|> > contiendrait alors un pointeur à cet objet, et chaque type de
|> > Graph pourrait commencer par :

|> > if ( c.tripletInfo->supportedGraphTypes.find( &typeid( *this ) )
|> > == c.tripletInfo->supportedGraphTypes.end() ) {
|> > throw InvalidCurveType ;
|> > }

|> > // ...

|> > setLegent( c.tripletInfo.libelle ) ;

|> > Mais ça met l'information sur les types de graphes supportés dans
|> > les classes ABC, DEF, etc. Je ne sais pas assez de l'application
|> > pour savoir si c'est bien ou non. Si l'information doit être dans
|> > les Graph, on peut l'y mettre aussi. Ou on pourrait se servir d'un
|> > map indépendant des deux.

|> Après réflexion j'avais opté pour que ce soient les graphes qui
|> disent ce qu'ils savent représenter, et pas les Triplets qui
|> autorient les graphes à les représenter.

C'est en fait une décision de conception qui dépend beaucoup de
l'application, et de ce qu'on compte en faire.

|> Utiliser typeid est intéressant, mais j'ai peur que cela fasse
|> double emploi avec un
|> static string Triplet::TypeName
|> qui est obligatoire car le nom peut différer de la classe.

D'une certaine côté, oui. De l'autre, tu es garanti par le langage que
les type_info compare égal si ce sont les mêmes types, et inégal
autrement. Tandis qu'avec TypeName, tu comptes sur l'utilisateur de
faire correctement.

|> C'est plus robuste qu'une simple comparaison de string, mais c'est
|> peut être un peu complexe / démesuré pour mon cas, et ça augmente le
|> couplage.

|> Si je récapitule :
|> - le lien entre les graphes et les Triplets est fait par la classe
|> Curve
|> - cette classe est construite par une fonction template à partir de
|> n'importe quelle liste de Triplets.

|> Si demain je dois représenter une courbe de plus qui n'est pas issue
|> de Triplets, c'est facilement faisable car Curve n'a absolument pas
|> connaissance des Triplets.

Je croyais que Curve contenait un std::vector< Triplet >.

|> Le typage par une chaine de caractère me parrait plus souple.

C'est plus souple. On peut, par exmple, mentir ; on pourrait avoir deux
classes de valeur dont le TypeName est égal. Selon l'évolution que prend
l'application, ça peut être un avantage ou un désavantage.

|> L'esprit de cette approche n'est pas :
|> - dessiner la courbe représentant la liste de Triplets ABC, mais
|> - dessiner la courbe "ABC" construite à partir d'une liste de Triplets ABC
|> Ainsi les graphes disent juste "moi je sais dessiner la courbe "ABC", peu
|> importe qu'elle soit construite depuis une liste de Triplets ABC ou lue
|> depuis un fichier.

D'accord. Ça me semble assez logique. Reste que le jour où tu ajoutes un
nouveau type de Triplet, il va falloir modifier tous les Graph qui
doivent pouvoir l'afficher.

Je n'écarterais pas d'office non plus la solution avec un map à part.

|> > J'aurais tendance à en faire une fonction membre statique. Voire
|> > carrément de mettre le code dans un constructeur templaté. À
|> > condition, évidemment, d'avoir un compilateur à peu près à jour,
|> > qui supporte des membres templatés (mais ça devient de moins en
|> > moins un problème).

|> Oui, c'est ce que m'a proposé Christophe, et ce que j'ai découvert
|> au passage (fonction membre template dans une classe qui ne l'est
|> pas).

|> > > J'essaye de travailler dans ce sens : améliorer les choses. Mais
|> > > certains pensent que c'est déjà parfait.

|> > Évidemment, puisque c'est eux qui l'ont fait:-).

|> > L'importance, c'est de se rendre compte que ce n'est jamais
|> > parfait. Même quand on l'a fait soi-même. Il y a toujours des
|> > contraits externes, de temps ou d'autres choses, qui font qu'on
|> > aurait pu faire mieux, si.

|> > L'importance, aussi, c'est de se rendre compte que nous ne
|> > connaissons jamais les conditions et les contraintes qui étaient
|> > présentes quand le code était écrit. On ne peut donc jamais dire
|> > qu'on aurait fait mieux. Tout code est ce qu'il est. Il a une
|> > histoire, qu'on ne connaît jamais parfaitement, mais qui explique
|> > pourquoi il est ce qu'il est. L'importance, ce n'est pas comment
|> > il y est arrivé ; c'est où on va de là. Il faut savoir accepter ce
|> > qu'ont fait les autres. Ce n'est pas « leur code, c'est de la
|> > merde », mais « s'ils n'avaient pas eu tellement de contraites
|> > externes, ils l'auraient sûrement écrit différemment. »

|> Ce code a été développé par quelqu'un qui utilise ses libs persos
|> qui doivent être issues de 20 ans de développement. Le problème
|> c'est que ces "snippets" n'ont pas trop été revus en 20 ans.
|> Template, STL, std::string, membres const, ...rien de tout cela
|> (mais malloc/free, char * de taille fixe, macros à gogo). C'est ce
|> qui me frustre, que ça n'ait pas été mis au goût du jour. Je pense
|> que le principal boulot d'un informaticien est de se tenir à jour. A
|> quoi ça sert de faire évoluer la norme sinon ?

À casser le code existant ? :-)

En fait, je me doutais un peu de cette histoire quand tu m'as dit qu'il
était plein de macros -- dans le temps, on se servait des macros pour
émuler les templates.

Quant à la mise à jour, il y a un vieux dicton en anglais : « if it
ain't broke, don't fix it ». Il y a prèsque toujours des choses plus
importantes et plus urgentes à faire que de mettre à jour du code qui
marche, et dont on n'a pas besoin de modifier sa fonctionalité.

Maintenant, il faut bien se rendre compte de ce qui était acceptable, ou
même très bien, il y a un certain temps, ne l'est plus selon les normes
d'aujourd'hui. Ce n'est pas forcement une raison de se précipiter à le
changer, mais c'est une chose à prendre en compte quand il faut le
changer.

--
Avatar
drkm
writes:

Mais ici aussi, j'aurais peut-être une approche un peu différente. Tu as
de différents types de Triplet, et de différents types de Graph. Alors,
on pourrait imaginer que pour chaque type de Triplet, on associe un
std::set< std::type_info const* >, avec les types des graphes auquels le
Triplet peut être associé. Voire...


Je n'ai suivi la discussion que de loin, mais il me semble qu'il
s'agit ici d'un problème de typage *à la compilation*. Ne vaudrait-il
pas mieux utiliser quelque chose comme les TypeList d'Alexandrescu ?

[...]

Autrement dit, une fois qu'il existe, le code n'est ni bon, ni
mauvais.


Ben heu, si, quand même un peu ...

--drkm

Avatar
kanze
drkm wrote in message
news:...
writes:

Mais ici aussi, j'aurais peut-être une approche un peu différente.
Tu as de différents types de Triplet, et de différents types de
Graph. Alors, on pourrait imaginer que pour chaque type de Triplet,
on associe un std::set< std::type_info const* >, avec les types des
graphes auquels le Triplet peut être associé. Voire...


Je n'ai suivi la discussion que de loin, mais il me semble qu'il
s'agit ici d'un problème de typage *à la compilation*. Ne vaudrait-il
pas mieux utiliser quelque chose comme les TypeList d'Alexandrescu ?


Je ne sais pas ; je ne connais pas TypeList. Étant obligé à utiliser des
compilateurs assez anciens, et d'écrire du code que peuvent comprendre
des collègues qui ne veulent pas se mettre à jour, j'ai rarement
l'occasion de pouvoir profiter de ce que fait Andrei. Est-ce une
implémentation de ce que je propose ?

[...]

Autrement dit, une fois qu'il existe, le code n'est ni bon, ni
mauvais.


Ben heu, si, quand même un peu ...


C'est un point de vue, et c'est sûr qu'il y a des différences. Mais ce
que je voulais dire, c'est qu'il ne faut pas juger -- bon et mauvais
sont rélatif, et c'est tout à fait possible que ce qu'on jugerait bon
était en fait le meilleur possible dans les conditions où il a été
écrit.

La question ne doit jamais être, « est-ce que l'existant est bon ou
mauvais », mais « qu'est-ce qu'il faut faire pour qu'il soit meilleur la
prochaine fois ». Et ça, même si tu juges le code déjà très bon.

--
James Kanze GABI Software http://www.gabi-soft.fr
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
Voici la solution que j'ai choisi d'adopter.

Rappel du problème :
On a des triplets de différents types : ABC, DEF, GHI, ... Ils ont les
particularités suivantes :
- ils ont tous 3 valeurs (double)
- ils sont tous calculés à partir de ABC
- leurs valeurs n'ont rien en commun et ils ne sont pas comparables (ABC en
kilomètres, DEF en poucentage, GHI
en degrés...)

On veut une solution la plus générique possible (par exemple une seule
fonction Calcule() et Affiche() qui puisse
calculer et afficher n'importe quel type de triplet).

Solution adoptée (sur le schéma UML)
- tous les Triplets implémentent l'interface ITriplet
- les autres triplets que ABC ne sont pas construits à partir de ABC mais
tous les triplets sont construits à partir de
3 valeurs a, b et c, y compris ABC (qui n'a donc pas grand chose à faire).
- l'interface ITriplet définit (entre autre) que chaque triplet doit se
construire à partir de A, B et C + une méthode GetValue qui renvoie une
valeur
du triplet.

Réalisation en C++

Au départ j'avais opté pour une classe abstraite ITriplet (Abstract Base
Class). Mais ceci a des
inconvénients :
- le recours aux pointeurs est obligatoire
- les triplets peuvent être mélangés (rien n'empêche de pusher un DEF dans
un vector<ITriplet*> destiné à ne
contenir que des ABC). Il suffit d'un vector<ABC> me direz-vous, mais alors
on perd tout l'intérrêt (généricité..
.)

Donc j'ai opté pour une solution à base de template.
L'interface ITriplet n'existe nul part, elle est purement logique. Elle est
vérifiée par les classes et fonctions
template qui l'utilise. Je cherche un meilleur moyen mais c'est le sujet
d'une autre question.
Merci à tous ceux qui m'ont aidé, et en particulier à James.

// triplet ABC
class ABC
{
public:
ABC( double A, double B, double C )
{
data.reserve( 3 );
data.push_back( A );
data.push_back( B );
data.push_back( C );
}
double GetValue( int Index ) const
{
return this->data.at( Index );
}
private:
std::vector<double> data;
};

// triplet DEF
class DEF
{
public:
DEF( double A, double B, double C )
{
data.reserve( 3 );
double total = A + B + C;
data.push_back( A / total );
data.push_back( B / total );
data.push_back( C / total );
}
double GetValue( int Index ) const
{
return this->data.at( Index );
}
private:
std::vector<double> data;
};

// calculateur générique de triplets (exemple)
class TripletBuilder
{
public:
TripletBuilder() : a(5.0), b(7.0), c(4.0)
{
}

template<typename T>
T Compute( int Num ) const
{
return T(
this->a * Num,
this->b * Num,
this->c * Num );
}

template<typename T>
std::vector<T> ComputeAll()
{
std::vector<T> result;
for ( int i = 0; i < 10; ++i )
{
result.push_back( this->Compute<T>( i ) );
}
return result;
}

private:
double a;
double b;
double c;
};

// fonction générique d'affichage (exemple)
void Print( std::vector<double> Values )
{
for ( size_t i = 0; i < Values.size(); ++i )
{
std::cout << Values[ i ] << 't';
}
std::cout << 'n';
}

// fonction permettant l'affichage générique
template<typename T>
std::vector<double> SelectTripletValues( std::vector<T> Values, int Index )
{
std::vector<double> result;
for ( size_t i = 0; i < Values.size(); ++i )
{
result.push_back( Values[ i ].GetValue( Index ) );
}
return result;
}

// exemple d'utilisatioint main()
int main()
{
TripletBuilder builder;
ABC abc = builder.Compute<ABC>( 0 );
DEF def = builder.Compute<DEF>( 0 );
std::vector<ABC> vect = builder.ComputeAll<ABC>();
Print( SelectTripletValues<ABC>( vect, 0 ) );
}


--
Aurélien REGAT-BARREL
Avatar
usenet
wrote in message news::

Désolé pour la réponse tardive, mais ton article m'avait échappé.
J'en profite pour signaler que j'ai des problèmes avec Gnus et mon
serveur Usenet. Je m'excuse d'avance si je ne répond pas à quelqu'un.

drkm wrote in message
news:...

Je n'ai suivi la discussion que de loin, mais il me semble qu'il
s'agit ici d'un problème de typage *à la compilation*. Ne vaudrait-il
pas mieux utiliser quelque chose comme les TypeList d'Alexandrescu ?


Je ne sais pas ; je ne connais pas TypeList. Étant obligé à utiliser des
compilateurs assez anciens, et d'écrire du code que peuvent comprendre
des collègues qui ne veulent pas se mettre à jour, j'ai rarement
l'occasion de pouvoir profiter de ce que fait Andrei.


C'est bien dommage. Mais même dans ces conditions, je pense que la
lecture de "Modern C++ Design" est des plus intéressantes. Débutant,
ce fut mon premier réel contact avec les templates, dans un contexte
autre que le simple paramétrage d'un conteneur sur un type. Le texte
n'est pas très conséquent, se lit vite, mais c'est du concentré.

Est-ce une
implémentation de ce que je propose ?


Ma fois, je ne sais pas. Je n'avais déjà suivi cette discussion que
de loin il y a deux semaines. Là, je n'ai pas le courage de reprendre
le fil depuis le début. En deux mots, les Typelists sont des sortes
de listes chaînées de types. Un noeud de la liste est :

template < class T , class U >
struct Typelist
{
typedef T Head ;
typedef U Tail ;
} ;

NullType est défini comme valeur sentinelle de fin.

Pour définir la liste, on utilise évidemment la récursion :

typedef Typelist< char , Typelist< signed char , unsigned char > >
CharList ;

Il existe dans Loki une série de macros pour linéariser la
déclaration d'une liste :

typedef TYPELIST_3( char , signed char , unsigned char )
CharList ;

C'est *LE* point faible des Typelists. L'en-tête correspondant de
Loki contient 50 définitions de macros :

#define TYPELIST_1(T1) Typelist< T1 , NullType >
#define TYPELIST_2(T1,T2) Typelist< T1 , TYPELIST_1(T2) >
...
#define TYPELIST_50(T1,T2,...,T50)
Typelist< T1 , TYPELIST_49(T2,...,T50) >

Pour te faire une idée des outils qu'il définit sur ces listes de
types, voici un extrait de la table des matières, contenant le liste
des sections du chapitre sur les Typelists :

Chapter 3 Typelists
3.1 The Need for Typelists
3.2 Defining Typelists
3.3 Linearizing Typelist Creation
3.4 Calculating Length
3.5 Intermezzo
3.6 Indexed Access
3.7 Searching Typelists
3.8 Appending to Typelists
3.9 Erasing a Type from a Typelist
3.10 Erasing Duplicates
3.11 Replacing an element in a Typelist
3.12 Partially Ordering Typelists
3.13 Class Generation with Typelists
3.14 Summary
3.15 Typelist Quick Facts

Il s'en sert par exemple pour spécifier la liste des types
d'arguments de ses foncteurs généralisés.

--drkm


1 2 3 4 5