OVH Cloud OVH Cloud

Deriver d'un conteneur de la STL

55 réponses
Avatar
Aurélien REGAT-BARREL
Bonjour,
J'aimerais bien créer quelques classes qui dérivent de certains conteneurs
de la STL. Je sais que ces derniers ne sont pas conçus pour être dérivés,
mais sachant que le but est simplement de :
- créer un vrai nouveau type au lieu d'un typedef
- ne pas ajouter de donnée membre mais uniquement dfes fonctions

est-ce une pratique acceptable ?

Exemple:

class SortedData : public std::set<int>
{
public:
SortedData( const std::vector<int> & Vect )
{
this->insert( Vect.begin(), Vect.end() );
}

// calcule la moyenne
double Average() const
{
int total = 0;
total = std::accumulate( this->begin(), this->end(), total );
return total * 1.0 / this->size();
}
};


--
Aurélien REGAT-BARREL

10 réponses

1 2 3 4 5
Avatar
Fabien LE LEZ
On Fri, 21 Jan 2005 14:04:20 +0100, "Aurélien REGAT-BARREL"
:

Ah oui j'avais pas pensé à l'alloc dynamique.


Ben... C'est pourtant le problème avec le destructeur non virtuel :

Base* ptr= new Dérivée;
delete ptr;


--
;-)

Avatar
Aurélien REGAT-BARREL
Qu'en sais-tu ?

C'est pas parce qu'un destructeur semble vide (vu par le programmeur)
que l'appel à delete ne fait rien.


Ah oui j'avais pas pensé à l'alloc dynamique.

--
Aurélien REGAT-BARREL

Avatar
kanze
Marc Espie wrote:
In article <41f03e39$0$29123$,
Aurelien REGAT-BARREL wrote:

Si l'héritage est limité à un héritage d'implémentation
comme dans l'exemple de code posté, le plus simple est
quand même de faire un héritage privé qui évite de se poser
ce genre de questions.


J'y ai pensé. Mais je pense avoir au moins besoin de begin()
et end(), de l'itérateur (+ const_iterator), dans le cas de
vector de at() et operator[], ...
J'ai pas envie de m'embêter à ajouter des using au fur et à
mesure que j'ai besoin d'un truc...


Dommage, c'est pourtant ce qu'il faudrait faire.


Ou plutôt, les mettre tous, dès le départ.

À vrai dire, je ne me sers même pas de l'héritage pour ce genre
de chose. J'ai un membre, et j'écris toutes les fonctions de
renvoi. Mais j'avoue que c'est pénible ; avec l'héritage privé,
using simplifie beaucoup.

`m'sieur, m'sieur, je veux faire un truc crade, je peux.'
`oui, mais c'est pas sur que ca va marcher.'
`ben si, ca va marcher, dites-moi que ca va marcher.'
`ben non, c'est un truc crade.'

Deja, les heritages prives pour l'implementation, c'est une
technique bien specifique au C++ (plus propre de mettre le
detail d'implementation comme champ de l'objet et de deleguer
toutes les methodes vraiment utilisees au champ), alors si en
plus tu preferes faire de l'heritage public...

d'experience, tot ou tard, ca va te jouer des tours. Et
generalement, quand tu auras bien abuse de la situation, et
que tu te retrouveras dans une situation tordue dans 3000 ou
4000 lignes de code).

Oui, c'est plus `lourd' de devoir tout ecrire, mais ca ne
change rien cote performances, et ca rend les choses bien plus
explicites.

Choisis ton camp...


Sans doute ça dépend de l'application. Je ne permettrais jamais
une chose comme il prévoit dans un code de production. Mais pour
une solution « quick and dirty », qu'on va jeter une fois qu'il
aurait servi ? (Mais il faut se méfier des « on va le jeter ».
Il ne faut pas oublier qu'un des « on va le jeter » est devenu
MS-DOS, et les origines d'Unix n'en sont pas loin non plus.)

--
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
Dommage, c'est pourtant ce qu'il faudrait faire.

`m'sieur, m'sieur, je veux faire un truc crade, je peux.'
`oui, mais c'est pas sur que ca va marcher.'
`ben si, ca va marcher, dites-moi que ca va marcher.'
`ben non, c'est un truc crade.'

Deja, les heritages prives pour l'implementation, c'est une technique bien
specifique au C++ (plus propre de mettre le detail d'implementation comme
champ de l'objet et de deleguer toutes les methodes vraiment utilisees
au champ), alors si en plus tu preferes faire de l'heritage public...

d'experience, tot ou tard, ca va te jouer des tours. Et generalement,
quand

tu auras bien abuse de la situation, et que tu te retrouveras dans une
situation tordue dans 3000 ou 4000 lignes de code).

Oui, c'est plus `lourd' de devoir tout ecrire, mais ca ne change rien
cote performances, et ca rend les choses bien plus explicites.

Choisis ton camp...


Eh beh j'ai choisi, et le bon camp apparement :-) J'ai décidé de faire des
using, comme ça je peux rendre innaccessibles les méthodes qui modifient
l'objet, ce qui me permet d'être assuré d'un état cohérent de celui-ci tout
au long de son utilisation.

--
Aurélien REGAT-BARREL

Avatar
kanze
wrote:
est-ce une pratique acceptable ?


Je me pose également cette question...

Je fais actuellement autre chose et je me demande si c'est à
faire...

En fait je mets ma liste comme membre privé de ma classe et je
fournis des interfaces qui donnent accès à ma liste...

Je me dis que ma méthode n'est pas clair... et que c'est même
plutôt moche, non ?


Pourquoi ? Est-ce que l'utilisateur utilise ta classe, ou
std::list ? Qui en est résponsable de l'interface ?

Plus intéressant : supposons que pour une raison quelconque, tu
sois amené à changer le type de la collection.

En plus si je veux parcourir ma liste de l'extérieur je suis
obligé de fournir les itérateurs begin et end en public

Que pensent les gourous de ça ?


Ce que tu décris, c'est la solution classique. Dans la norme,
ils ont ajouté une signification à using qui permet à faire plus
simplement avec l'héritage privé. Mais il faut toujours se poser
la question : combien de mon implémentation est-ce que je veux
révéler ? Donc, par exemple, je continue à utiliser un membre
privé, avec un typedef pour l'itérateur, et une documentation en
ce que moi, je veux garantir de l'itérateur (et non ce que
garantit le membre privé) -- typiquement, je ne garantis que
forward iterator, même si pour l'instant, dans la classe, j'ai
un std::vector.

Du moment que tu dis à l'utilisateur qu'il a un
std::list<T>::iterator, autant rendre le membre public.

--
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
Faire un "delete" d'un pointeur de classe de base pointant sur un
objet de classe dérivée engendre un comportement indéfini sauf si
la classe de base comporte un destructeur virtuel.


Ca je le sais. Ce que je ne comprenais pas c'est comment ça pouvait poser
problème avec une classe & son destructeur vides :

class Base
{
public:
int Data;
};

class A : public Base
{
public:
void print() { std::cout << this->Data << 'n'; }
};

Base * b = new A;
delete b;

Etant donné que A ne fait rien de spécial, j'avais du mal à comprendre
comment le code ci-dessus pouvait poser problème. J'ai d'ailleurs toujours
pas trop compris, mais je peux concevoir que new/delete font des trucs qui
peuvent être à l'origine de problèmes (implémentation interne du RTTI ?)

--
Aurélien REGAT-BARREL

Avatar
Olivier Azeau
Aurélien REGAT-BARREL wrote:
Faire un "delete" d'un pointeur de classe de base pointant sur un
objet de classe dérivée engendre un comportement indéfini sauf
si


la classe de base comporte un destructeur virtuel.


Ca je le sais. Ce que je ne comprenais pas c'est comment ça pouvait
poser

problème avec une classe & son destructeur vides :


Et un truc dans ce genre ne pourrait pas résoudre le pb ?

template <class C>
class InheritableContainer : protected C
{
public:
typedef typename C::iterator iterator;
virtual ~InheritableContainer() {}
iterator begin() const { return C::begin(); }
// exposer toutes les méthodes nécessaires en tant que 'type'
visible de l'extérieur
};

class SortedData : public InheritableContainer< std::set<int> >
......

L'héritage 'protected' doit permettre de propager l'héritage
d'implémentation pour ne pas avoir a exposer toutes les méthodes du
container utilisées dans SortedData.

Enfin bon, je propose ca mais je ne pense pas que je me risquerais a
faire ce genre de manips...


Avatar
Jean-Marc Bourguet
"Aurélien REGAT-BARREL" writes:

Faire un "delete" d'un pointeur de classe de base pointant sur un
objet de classe dérivée engendre un comportement indéfini sauf si
la classe de base comporte un destructeur virtuel.


Ca je le sais. Ce que je ne comprenais pas c'est comment ça pouvait poser
problème avec une classe & son destructeur vides :

class Base
{
public:
int Data;
};

class A : public Base
{
public:
void print() { std::cout << this->Data << 'n'; }
};

Base * b = new A;
delete b;

Etant donné que A ne fait rien de spécial, j'avais du mal à comprendre
comment le code ci-dessus pouvait poser problème. J'ai d'ailleurs toujours
pas trop compris, mais je peux concevoir que new/delete font des trucs qui
peuvent être à l'origine de problèmes (implémentation interne du RTTI ?)


Il y a peu de chances(*) que le code ci-dessus pose un probleme. Les
choses qui ont des chances de poser des problemes:

- meme si le destructeur est absent, celui genere pour la classe
derivee peut faire plus que celui de la classe de base. C'est
douteux que ce soit le cas si aucun membre donnee ou virtuel est
ajoute.

- il peut y avoir un decalage entre le pointeur vers la classe de
base et celui de la classe derivee. A nouveau douteux dans des
cas simples, mais si on utilise de l'heritage multiple, c'est
certain que ce sera le cas pour au moins une des bases. Et alors
avec un destructeur virtuel, delete doit fonctionner, sans c'est
certain que ca va foirer pour au moins une des bases.

A+

(*) J'ai bien ecrit "chance" et pas "risque", parce que avec la
maintenance on va passer naturellement d'un cas a l'autre sans le
remarquer. Et alors ca peut etre difficile de voir pourquoi ce
changement sans consequence fait foirer quelque chose d'apparemment
non lie (les problemes, quand on en cause avec l'allocation memoire,
ont la sale manie d'etre visible a des endroits bien differents de
celui qui les cause). Naturellement, ca c'est le cas favorable ou le
pb est identifie par les tests d'avant check-in, sinon, c'est encore
pire.

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
kanze
wrote:
J'ai une classe "Charge" qui décrit des charges
électrostatique J'ai une classe "Charges" qui est une
"collection" de toutes les charges. Depuis mon programme
principal il faut je j'affiche chaque charge... Donc il faut
parcourir "Charges" depuis l'extérieur...


typedef std::list<Charge> Charges;

ça suffit pas ?


non ça ne suffit pas car il me faut aussi des méthodes...
Quelle est la charge la plus proche de (x,y) ?
Est-ce que (x,y) est proche d'une charge ?
Dessiner toutes les charges...


Ces fonctions-là doivent-elles en être membres ? J'aurais vu
plutôt des fonctions libres ; éventuellement même des fonctions
template (à deux itérateurs). (Pour la première, je me démande
même s'il ne faut pas une fonction find_best, puis un object
fonctionnel pour comparer la distance -- bind2nd de Distance,
peut-être. Pour la deuxième, il y a déjà find_if, et pour la
troisième, for_each. Chaque fois avec le Predicate ou l'objet
fonctionnel qui convient.)

--
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
Jean-Marc Bourguet
"" writes:

J'ai une classe "Charge" qui décrit des charges électrostatique
J'ai une classe "Charges" qui est une "collection" de toutes les charges.
Depuis mon programme principal il faut je j'affiche chaque charge...
Donc il faut parcourir "Charges" depuis l'extérieur...
typedef std::list<Charge> Charges;

ça suffit pas ?



non ça ne suffit pas car il me faut aussi des méthodes...
Quelle est la charge la plus proche de (x,y) ?
Est-ce que (x,y) est proche d'une charge ?
Dessiner toutes les charges...


Il me semble que rendre le fait que ta representation actuelle est une
liste *n'est pas* un bon choix. Par exemples, une modification qui
devrait etre transparente est le changement de la structure de donnee
pour rendre la recherche de la charge la plus proche ou des charges
visibles dans une fenetre plus efficace.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org



1 2 3 4 5