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

2 3 4 5 6
Avatar
kanze
Falk Tannhäuser wrote:
Aurélien REGAT-BARREL wrote:
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();
}
};


Pourquoi Average() doit-elle absolument être une fonction
membre ? Quelque chose comme

template<typename Container>
double Average(Container const& c)
{
return std::accumulate(c.begin(), c.end(),
static_cast<typename
Container::value_type>(0))

/ double(c.size());
}

ne fait-elle pas l'affaire ?


Parce qu'il impose une collection ?

Je verrais plutôt quelque chose du genre :

| template< typename T >
| class Averager
| {
| public:
| Averager() : v( 0 ), n( 0 ) {}

| Averager& operator+=( T a )
| {
| v += a ;
| ++ n ;
| return *this ;
| }

| operator T() const
| {
| return v / n ;
| }
| private:
| T v ;
| size_t n ;
| } ;

| template< typename T >
| Averager< T >
| operator+( Averager< T > const& l, Averager< T > const& r )
| {
| Averager< T > result( l ) ;
| result += r ;
| return result ;
| }

avec par la suite :

| double d = std::accumulate( c.begin(),
| c.end(),
| Averager< double >() ) ;

(Au fond, je trouve accumulate bien sympa ; pour une fois, même
le nom convient. Je m'en sers donc pour le calcul de CRC ou de
MD5 -- mes classes ne fournissent que des accumulateurs
spéciaux.)

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

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


Ça peut aussi arriver dans le cas d'héritage virtuel, ou encore
si une classe polymorphique dérive d'une classe non polymorphique
(c.a.d. si au moins une fonction virtuelle est ajoutée dans la
classe dérivée, les containers standard n'en comportant pas).

Falk

Avatar
Falk Tannhäuser
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 p oser
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;


Si dans ta classe A tu as redéfini new et delete :

class A : public Base
{
public:
void print() const { ... }

static void* operator new(std::size_t size)
{ return my_special_alloc(size); }

static void operator delete(void* ptr)
{ my_special_free(ptr); }
};

l'opérateur redéfini A::new sera appelé dans l'expression
Base* b = new A;
mais l'appel de A::delete n'est garanti dans
delete b;
que si Base a un destructeur virtuel ; sinon c'est opérateur
global ::new qui risque d'être appelé et rien ne dit que
celui-ci traite correctement les blocs mémoire alloués
par my_special_alloc comme le ferait my_special_free
(d'où risque de fuite de mémoire, de corruption des données
ou de plantage).

Falk


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



justement il me semble que ma représentation encapsule la liste...

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


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


Qu'est-ce qu'un objet fonctionnel ?
bind2nd ?

Pour la deuxième, il y a déjà find_if,


et pour la
troisième, for_each.


oui

Chaque fois avec le Predicate ou l'objet
fonctionnel qui convient.)


j'avoue ne pas être très à l'aise avec ce concept de "Predicate"

Avatar
Fabien LE LEZ
On Fri, 21 Jan 2005 16:44:25 +0100, ""
:

justement il me semble que ma représentation encapsule la liste...


Si elle encapsule correctement la liste, l'utilisateur de la classe ne
doit même pas être au courant qu'il y a un membre de type std::list<>.

En fait, dès que tu vois un truc du style

class C
{
public:
int GetX() const { return x; }
void SetX (int nouveau_x) { x= nouveau_x; }
private:
int x;
};

demande-toi si casser ainsi l'encapsulation (i.e. demander à
l'utilisateur de la classe de connaître la présence du membre privé x)
est vraiment une bonne chose. Ça l'est rarement.


--
;-)

Avatar
noone
Charges ma_liste;
ma_liste.Affiche (cout);




En fait le problème n'est pas l'affichage mais le calcul des champs E et B.

Je le fait dans le prog principal car il faut connaitre la grille pour
le calcul...

Avatar
noone

demande-toi si casser ainsi l'encapsulation (i.e. demander à
l'utilisateur de la classe de connaître la présence du membre privé x)
est vraiment une bonne chose. Ça l'est rarement.




et je fais comment alors pour interagir avec ma grille pour faire le
calcul de E et B ?

Avatar
Jean-Marc Bourguet
"" writes:

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.



justement il me semble que ma représentation encapsule la liste...


class SortedData : public std::set<Charge>

n'est certainement pas une encapsulation. Meme

class SortedData : private std::set<Charge>

n'en est pas si tu publies que tu fournis des std:set<int>::iterator.
En fait j'aurais meme tendance a ne pas me contenter d'un typedef.
L'objectif est de pouvoir remplacer le std::set<Charge> par un
RTree<Charge> sans probleme.

Je n'heriterais meme pas, et je n'appellerais pas la class SortedData.

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


Avatar
Fabien LE LEZ
On Fri, 21 Jan 2005 16:48:20 +0100, ""
:

pourquoi 2 itérateurs ?


C'est l'idiome STL : toute fonction qui doit agir sur un ensemble
d'éléments prend comme paramètre un itérateur sur le premier élément
et un itérateur sur l'élément "past the end" (i.e. l'itérateur qui
suit directement le dernier élément).



--
;-)

2 3 4 5 6