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
Aurelien REGAT-BARREL
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 ?

--
Aurélien REGAT-BARREL

Avatar
Aurelien REGAT-BARREL
Oui, car le compilateur va générer un destructeur par défaut non virtuel,
donc le problème demeurera.


Oui mais vu que ce destructeur ne fait rien où est le problème ?

--
Aurélien REGAT-BARREL

Avatar
Aurelien REGAT-BARREL
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...

--
Aurélien REGAT-BARREL

Avatar
Aurelien REGAT-BARREL
Pourquoi Average() doit-elle absolument être une fonction
membre ?


Parce que là c'était un exemple. Les fonctions que je compte mettre sont
hyper spécifiques à l'utilisation que je fais du conteneur. Comme le dit
Olivier son utilisation relève du détail d'implémentation et l'héritage
mériterait d'être protégé / privé, mais je compte le laisser public par
commodité (bénéficier simplement de begin(), end(), iterator, at(),
operator[], size(), ...).
L'idée est aussi de rendre le truc un peu plus objet et de regrouper dans un
même fichier les opérations portant sur cette utilisation particulière d'un
conteneur.
Exemple simple: j'ai déjà des fonctions dans le style de ce que tu as donné
(fonctions plus ou moins "libres" situées quelque part) et un simple typedef
dans ce style:

typdef std::set<int> SortedData;

double Special1( SortedData const & );
double Special2( SortedData const & );
double Special3( SortedData const & );

Donc déjà on verrait bien ces fonctions comme membres de SortedData. Mais
aussi par exemple SortedData ne doit pas être vide, et donc partout où je le
reçois en paramètre je suis obligé de faire des assertion() comme quoi il
est pas vide. En créant un constructeur dans le style de celui que j'ai
donné je peux m'assurer qu'il n'est jamais vide, et l'itérer sans risque...
Ca rejoint ma volonté de "conversion au RAII" comme quoi un objet créé avec
succès doit être utilisable sans devoir tester s'il se trouve dans un état
valide ou non.

--
Aurélien REGAT-BARREL

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

etc...


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

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


Avatar
Fabien LE LEZ
On Thu, 20 Jan 2005 19:59:39 +0100, ""
:

Depuis mon programme principal il faut je j'affiche chaque charge...


Charges ma_liste;
ma_liste.Affiche (cout);


--
;-)

Avatar
Fabien LE LEZ
On Fri, 21 Jan 2005 00:22:56 +0100, "Aurelien REGAT-BARREL"
:

Oui, car le compilateur va générer un destructeur par défaut non virtuel,
donc le problème demeurera.


Oui mais vu que ce destructeur ne fait rien


Qu'en sais-tu ?

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


--
;-)


Avatar
kanze
Aurélien REGAT-BARREL wrote:
Tant que tu n'essaies pas de detruire un type derive a
partir d'un pointeur sur le type de base, il n'y a pas de
probleme de C++. Pour le reste, il faut connaitre la
situation.


A priori non, donc je vais faire ça alors.

Sinon juste pour info, si je n'ai aucune donnée membre et
aucun destructeur, ça poserait quand même un problème de
détruire depuis un pointeur de base ?


Selon la norme, c'est toujours un comportement indéfini. Dans la
pratique, si on n'ajoute que des fonctions non-virtuelles (y
compris des constructeurs), ça m'étonnerait que tu trouves une
implémentation où ça pose un problème.

--
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
Falk Tannhäuser
Aurelien REGAT-BARREL wrote:

Oui, car le compilateur va générer un destructeur par défaut non virtuel,
donc le problème demeurera.


Oui mais vu que ce destructeur ne fait rien où est le problème ?


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.
Dans la pratique, ce comportement indéfini peut se manifester de
différentes façons :
- Ça peut marcher comme voulu, au moins pendant les tests ou
jusqu'à la prochaine mise à jour du compilateur, puis ça planter a
une fois le soft est délivré chez le client.
- Le destructeur de la classe dérivée n'est pas appelé, seul celui
de la classe de base.
- Une mauvaise adresse peut être passée à la fonction de déalloc ation
car le sous-objet de classe de base contenu dans l'objet complet
ne se situe pas forcement au début de celui-ci. Ceci peut entraîne r
un plantage tôt ou tard, ou une corruption des données en mémoir e.
- Une mauvaise fonction de déallocation peut être appelée, surtout
lorsque les opérateurs new et delete sont redéfinis au niveau de
ta classe. Résultat observable possiblement comme au point
précédant.
- ???

Falk


1 2 3 4 5