Redéfinition d'une méthode dans une classe interne héritée

Résolu
mondrone Messages postés 246 Date d'inscription mercredi 5 janvier 2005 Statut Membre Dernière intervention 11 mars 2012 - 11 mars 2012 à 10:11
mondrone Messages postés 246 Date d'inscription mercredi 5 janvier 2005 Statut Membre Dernière intervention 11 mars 2012 - 11 mars 2012 à 16:37
Bonjour,

J'ai une classe qui ressemble à ca :

class mon_tableau<Val*> : public std::vector<shared_ptr <Val*> >
{
//Le but étant sans le cacher de faire un tableau à auto retain/release
}

Je voudrais que la méthode mon_tableau<Val*>::iterator::operator*() renvoi un Val* en non un shared_ptr<Val*>, ce qui passe forcément par une redéfinition de cette dernière.
C'est là que je bloque, je ne sais pas redéfinir une méthode de classe interne héritée, sans par ailleurs redéfinir complètement la classe interne.

J'ai essayé

class mon_tableau<Val*> : public std::vector<shared_ptr <Val*> >
{
class iterator
{
Val *operator *();
};
}

Mais il me dit que l'operator en question est private (auquel cas comment se fait-il que je puisse déclarer un std::vector::iterator et utiliser l'operator* dessus par ailleurs ?)
J'en ai conclu à une syntaxe erronée, et j'avoue que je ne vois pas vraiment comment faire autrement... ?

Par avance, merci :)

  Qui ne tente rien...
  Ne risque pas d'avoir grand chose !!!

10 réponses

cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
11 mars 2012 à 13:27
Bonjour.

Attention, on ne peut pas forcément hériter d'une classe. S'il n'y a pas de "virtual", hériter d'une classe, bien que techniquement possible n'est pas forcément la solution. Il est souvent préférable de créer un wrapper qui encapsule la classe dont l'on veut spécifier certains comportements.
Hériter de std::vector est rarement une bonne idée... Je ne peux que te conseiller de créer ta propre classe de vecteur qui encapsule un std::vector et qui fait ce que tu veux.

Néanmoins, c'est techniquement possible de faire ce que tu veux. C'est juste très moche et dur à maintenir/relire. Tout d'abord, il faut savoir que les classes iterator sont des classes templates réutilisées dans toutes la STL. Il n'y a donc pas une classe iterator par classe STL. Il existe d'ailleurs de nombreux type d'iterator (normal, reverse, à sens unique, à double sens, à accès direct, etc...).

En lisant le code de std::vector, on s'aperçoit que c'est un normal_iterator qui est utilisé. Il ne reste donc plus qu'à modifier son comportement. On va donc créer un release_iterator se basant sur le normal_iterator. La plupart des méthodes nécessaires (constructeurs, operator*, etc...) doivent être réécrite même si elle font la même chose (appel du comportement parent). En effet, les comportements ne "tombent" pas dans la classe fille dans le cas des constructeurs avec template. Enfin, tout ce code copié, va nous servir à réaliser une version spécifique de "operator*" de l'iterator.

Dans la classe qui hérite de std::vector, il ne reste plus qu'à écraser begin() et end() par nos propres méthodes qui retourneront non pas un normal_iterator, mais un release_iterator.

Ça donne ceci:
#include 
#include <vector>

// dumb class, to make this example works
template <typename T>
struct shared_ptr
{
  shared_ptr(T val)
    : _val(val)
  {
  }

  T release()
  {
    return _val;
  }

  T _val;
};

struct Val { Val(int i) : _i(i) {} int _i; };

template <typename T>
class release_iterator :
  public __gnu_cxx::__normal_iterator<typename std::vector<T>::pointer, std::vector<T> >
{
  typedef __gnu_cxx::__normal_iterator<typename std::vector<T>::pointer, std::vector<T> > super;

public:
  release_iterator()
    : super()
  {
  }

  release_iterator(const typename super::iterator_type& __i)
    : super(__i)
  {
  }

  // real behavior !
  // typename std::vector<T>::reference
  // operator*() const
  // {
  //   return *super::_M_current;
  // }

  // Ugly :(
  Val*
  operator*() const
  {
    return super::_M_current->release();
  }
};

class MonTableau : public std::vector<shared_ptr<Val*> >
{
  typedef std::vector<shared_ptr<Val*> > super;
public:
  typedef release_iterator<shared_ptr<Val*> > iterator;

public:
  iterator
  begin()
  {
    return iterator(this->_M_impl._M_start);
  }
  iterator
  end()
  {
    return iterator(this->_M_impl._M_finish);
  }
};

int main()
{
  MonTableau tab;
  tab.push_back(new Val(5));
  tab.push_back(new Val(12));

  MonTableau::iterator end = tab.end();
  for (MonTableau::iterator it = tab.begin(); it != end; ++it)
    std::cout << (*it)->_i << std::endl;

  return 0;
}


________________________________________________________________________
Historique de mes créations, et quelques articles:
[ http://0217021.free.fr/portfolio http://0217021.free.fr/portfolio]
Merci d'utiliser Réponse acceptée si un post répond à votre question
3
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
11 mars 2012 à 16:04
Ce qui signifie donc que c'est au développeur de s'arranger pour, s'il stock par exemple ses classes dans un tableau de classes "mère génériques", n'utiliser ces dernières que dans un contexte ou il connait déjà à quel type il a réellement affaire, pour avoir un gain de taille et de performance ?

Non. Ce n'est pas ce que je dit. Si tu es dans ce cas de figure, alors oui tu utiliseras virtual. Virtual est justement utile pour ce genre de cas.

Je dis simplement que virtual n'est pas forcément utile dans tous les cas de figure (et ils sont nombreux). Typiquement, si tu créés un conteneur générique (au hasard std::vector) personne ne va hériter de ta classe et donc l'emploi d'un virtual serait désastreux.
La plupart du temps, quand une classes est créée, il est rare qu'on hérite d'elle. Lorsque l'on veut spécialiser un type ou un comportement on utilise généralement un template. La redéfinition de méthode en C++ est une fonctionnalité moins commune que la spécialisation ou la simple encapsulation.

De plus, quand je dis que virtual dégrade les performances, ce n'est pas dramatique. Ce n'est un problème que dans des applications temps réels ou de l'embarqué. La plupart du temps la perte de performances est négligeable pour des applications "normales".

________________________________________________________________________
Historique de mes créations, et quelques articles:
[ http://0217021.free.fr/portfolio http://0217021.free.fr/portfolio]
Merci d'utiliser Réponse acceptée si un post répond à votre question
3
mondrone Messages postés 246 Date d'inscription mercredi 5 janvier 2005 Statut Membre Dernière intervention 11 mars 2012
11 mars 2012 à 13:45
Merci :)
C'est là que je vois que j'ai beau être bon en C, raisonner en C++ n'est pas toujours évident au départ !
J'avais justement pensé que le fait d'hériter permettait de ne redéfinir que les fonctions que l'on voulait changer, en gardant les autres identiques, de manière à ne pas devoir tout réécrire !
C'était sans compter ce mot clé "vitual" qui en plus de mémoire n'est pas présent pour les destructor de la STL, ce qui rend en effet l'héritage difficile.
(toujours du mal a comprendre pourquoi devoir spécifier vitual, la logique java me parait plus simple , mais c'est une autre histoire)

Merci en tout cas, je vais faire un wrapper, c'est en effet le mieux a faire dans mon cas .

Qui ne tente rien...  Ne risque pas d'avoir grand chose !!!
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
11 mars 2012 à 14:30
devoir spécifier virtual

Pour des raisons de performances.

Lorsque tu as une classe "normale" pas de piège. Les méthodes sont d'ailleurs bien redéfinis comme tu le penses (Je parlais des constructeurs). C'est uniquement quand tu mêles ça aux templates que ça devient compliqué :)

A partir d'un seul virtual dans ta classe, il y a création d'une vtable en interne. Il peut potentiellement y avoir perte de performance (surtout s'il n'y a personne pour hériter de ta classe...). En effet, dans cette table, sont mises toutes les méthodes virtuelles. Lors d'un appel à une méthode, on cherche dans la vtable, si on doit appeler la méthode de la classe mère ou fille, ce qui n'est pas gratuit.

Donc, ne pas avoir virtual ne gêne que dans un seul cas: lors de la réduction d'une classe fille dans une classe mère.

Ex:

#include 

struct A { void coucou() { std::cout << "A" << std::endl;} };
struct B : public A { void coucou() { std::cout << "B" << std::endl;} };
struct C { virtual void coucou() { std::cout << "C" << std::endl;} };
struct D : public C { void coucou() { std::cout << "D" << std::endl;} };

void normal()
{
  A a;
  B b;
  C c;
  D d;
  a.coucou(); // Affiche A
  b.coucou(); // Affiche B
  c.coucou(); // Affiche C
  d.coucou(); // Affiche D
}

void pointeur_normal()
{
  A* a = new A;
  B* b = new B;
  C* c = new C;
  D* d = new D;
  a->coucou(); // Affiche A
  b->coucou(); // Affiche B
  c->coucou(); // Affiche C
  d->coucou(); // Affiche D
}

void heritage()
{
  A* a = new B;
  C* c = new D;
  a->coucou(); // Affiche A, oupsss non prise en compte de la classe fille B
  c->coucou(); // Affiche D
}

int main()
{
  normal();
  pointeur_normal();
  heritage();

  std::cout << "Taille de A: " << sizeof (A) << std::endl // 1
    << "Taille de B: " << sizeof (B) << std::endl // 1
    << "Taille de C: " << sizeof (C) << std::endl // 4
    << "Taille de D: " << sizeof (D) << std::endl; // 4

  return 0;
}


Pour cette raison, la STL fonctionne avec du template et non de l'héritage virtuel. Les templates permettent de toute façon de réaliser bien plus que ce que permet l'héritage sans perte de performance :)

Attention, Java est un langage interprété qui peut se permettre des choses que l'on ne peut se permettre dans un langage compilé. (Par exemple pour le C++, mettre du virtual par défaut, aurait un effet désastreux puisque à moins de bien comprendre le "moteur interne", on se retrouverait avec des lenteurs inexpliquées. Java peut se le permettre puisqu'il n'est pas soumis au même contraintes).

________________________________________________________________________
Historique de mes créations, et quelques articles:
[ http://0217021.free.fr/portfolio http://0217021.free.fr/portfolio]
Merci d'utiliser Réponse acceptée si un post répond à votre question
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
mondrone Messages postés 246 Date d'inscription mercredi 5 janvier 2005 Statut Membre Dernière intervention 11 mars 2012
11 mars 2012 à 15:22
Oui, j'avais bien compris le principe, mais je n'ai pas encore trouvé de cas d'utilisation du "a->coucou(); // Affiche A, oupsss non prise en compte de la classe fille B"
Si on hérite puis redéfinie une méthode, c'est bien parce qu'elle fait des trucs en plus qui doivent être fait, je vois pas trop de cas d'utilisation pour lesquels le fait de "caster" B en A fait que l'on veuille effectivement retrouver complètement le fonctionnement des méthodes de A... ?

Qui ne tente rien...  Ne risque pas d'avoir grand chose !!!
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
11 mars 2012 à 15:33
Si on hérite puis redéfinie une méthode, c'est bien parce qu'elle fait des trucs en plus qui doivent être fait, je vois pas trop de cas d'utilisation pour lesquels le fait de "caster" B en A fait que l'on veuille effectivement retrouver complètement le fonctionnement des méthodes de A... ?

Le but n'est pas d'avoir ce comportement, qui est certes très peu utile, mais bien d'éviter de créer une vtable qui est coûteuse (en taille et en performance).
Or le comportement "c->coucou(); // Affiche D" ne peut se faire qu'à l'aide d'une vtable. Mettre du virtual par défaut, créerait potentiellement des vtables pour rien.

________________________________________________________________________
Historique de mes créations, et quelques articles:
[ http://0217021.free.fr/portfolio http://0217021.free.fr/portfolio]
Merci d'utiliser Réponse acceptée si un post répond à votre question
0
mondrone Messages postés 246 Date d'inscription mercredi 5 janvier 2005 Statut Membre Dernière intervention 11 mars 2012
11 mars 2012 à 15:48
Ce qui signifie donc que c'est au développeur de s'arranger pour, s'il stock par exemple ses classes dans un tableau de classes "mère génériques", n'utiliser ces dernières que dans un contexte ou il connait déjà à quel type il a réellement affaire, pour avoir un gain de taille et de performance ?

Ca ressemble un peu à "on fait de l'héritage pour pouvoir facilement avoir accès à telle fonctionnalité super pratique, qu'il vaut mieux éviter d'utiliser car elle est lente et gourmande", c'est ca ?


Qui ne tente rien...  Ne risque pas d'avoir grand chose !!!
0
mondrone Messages postés 246 Date d'inscription mercredi 5 janvier 2005 Statut Membre Dernière intervention 11 mars 2012
11 mars 2012 à 16:09
Ok, je comprend mieux.
Au moins, tu es le premier a me donner clairement une réponse à cette question :)


Qui ne tente rien...  Ne risque pas d'avoir grand chose !!!
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
11 mars 2012 à 16:28
Pour la vtable, vu que tu fais du C, je t'ai enfin retrouvé l'exemple d'une vtable simplifié dans ce langage.
(C'est un lien que je voulais te mettre en réponse à ton 2ème post, mais j'ai eu du mal à retrouver le lien).

http://www.cppfrance.com/forum/sujet-FONCTION-VIRTUELLE_1384710.aspx

________________________________________________________________________
Historique de mes créations, et quelques articles:
[ http://0217021.free.fr/portfolio http://0217021.free.fr/portfolio]
Merci d'utiliser Réponse acceptée si un post répond à votre question
0
mondrone Messages postés 246 Date d'inscription mercredi 5 janvier 2005 Statut Membre Dernière intervention 11 mars 2012
11 mars 2012 à 16:37
Oui, je connais déjà l'équivalent de la vtable en C :).
Ma question se tournait plus sur le cas du "cast en class mère", mais tu y a répondu ;)


Qui ne tente rien...  Ne risque pas d'avoir grand chose !!!
0
Rejoignez-nous