Forum Programmation.c++ Pure virtual method called

Posté par  .
Étiquettes : aucune
0
19
mai
2006
Bonjour bonjour !

Ce petit message fait suite à une longue prise de tête. Après plusieurs heures à me demander pourquoi mon executable finissait avec un SIGABRT avec un joli message "pure virtual method called", j'ai enfin isolé le bug. Le soucis est que je n'ai aucune idée pour le résoudre de façon propre.

Le problème est le suivant :

Soient 3 classes telles que:


class Base {
public:
Base() {
Client* client=new Client;
client->test(this);
delete client;
}
virtual ~Base() {}
virtual void pure() const=0;
};

class Deriv:public Base{
public:
Deriv():Base() { }
virtual void pure() const { cout << "test réussi" << endl }
virtual ~Deriv() {}
};

class Client{
public:
void test (Base* base) {
base->pure();
}
}


Dans mon application réelle, client est singleton et je m'en sers à chaque fois qu'une classe dérivée est construite pour "l'enregistrer" au sein d'un catalogue. J'ai tout de suite eu l'idée d'implémenter cette fonctionnalité dans le constructeur de la classe de base. Le soucis est que lors de l'appel à ce constructeur, this est du type (class Base *), et que lorsqu'on appelle une méthode à partir de ce "this", le principe de polymorphisme n'est pas mis en place, et c'est la méthode de la classe de base qui est appelée en lieu et place de celle de la classe dérivée. Sachant que pour mon appli, j'ai besoin de faire cet appel de méthode, j'ai un peu de mal à imaginer une façon "propre" de sortir de ce bourbier.

Des idées ?

Merci d'avance !

PS. Le code est disponible sur : http://lastrainson.info/bug.tar.gz
  • # Peut-être comme ça ?

    Posté par  . Évalué à 4.

    bonjour,

    tu pourrais déplacer le code que tu utilises dans le constructeur de la classe Pure, dans une fonction protected de la classe Pure ; et appeler cette fonction depuis le constructeur de Dervie

    class Base {
    public:

    Base() {}

    void init () {
    Client* client=new Client;
    client->test(this);
    delete client;
    }

    virtual ~Base() {}
    virtual void pure() const=0;
    };


    et le constructeur de Derive deviendrait :

    Deriv():Base() { this->init(); }


    je pense que ça devrait marcher.
  • # Manque d'info

    Posté par  (site web personnel) . Évalué à 3.

    Pas facile de te donner des conseils. En effet, tu as suffisament réduit le code pour ne garder que le cas d'école qui ne fonctionne pas.

    Du coup, impossible de te dire ce qui n'est pas "propre" dans ton cas.
    J'imagine qu'il y a diverses façon de sortir de ce "bourbier". Celle donnée précédemment en est une, basé sur les éléments que tu as fourni. Elle est donc à peine plus propre (ça reste un contournement, ce n'est pas élégant).

    Peut-être que si tu nous donne un peu plus d'info (noms des objets, noms des méthodes) histoire de comprendre un peu plus la métaphore de ton architecture...
    • [^] # Re: Manque d'info

      Posté par  . Évalué à 1.

      C'est vrai que pour le coup on peut dire que j'ai simplifié au maximum :-) .
      En gros, au sein de mon application, je gère différents types de données ayant une interface commune (classe abstraite de base de nom Data), répertoriées dans une classe Catalog de façon automatique. Le but de la manoeuvre est que le catalogue soit automatiquement rempli / vidé à chaque construction / destruction d'un objet dérivé de Data.

      Exemple : en instanciant une classe "Icon" dérivant de data, je veux que cette classe soit automatiquement ajoutée à mon catalogue, et qu'elle en soit retirée à sa destruction. Le tout sans que le développeur de la classe en question n'ait à connaître la façon dont fonctionne le catalogue, et sans non plus qu'il ait à faire les ajouts et suppressions manuellement lors du développement de sa classe.
      Le problème, c'est que le catalogue a besoin d'obtenir certaines informations sur l'instance à ajouter. Dans le cas présent, il s'agit simplement de récupérer une chaine identifiante, mais il est possible qu'on récupère d'autres informations par la suite. La méthode permettant d'obtenir cette ou ces informations est une méthode virtuelle pure définie dans Data.
      J'ai eu mon problème de "pure virtual method called" au moment de l'exécution car j'avais imputé directement au constructeur de Data la charge d'aller signaler la création de l'objet au catalogue. Sauf que lorsque le constructeur de Data est appelée, même si c'est en créant un Icon, le "this" représentant la classe en cours de construction est du type Data et non du type Icon. En appelant Catalog::getInstance()->addItem(this) qui va appeler à son tour le getInfo() du "this" passé en paramètre, on obtient une erreur car ce n'est pas le getInfo de Icon qui est appelé mais bel et bien celui de Data qui est virtuel pur. D'où mon petit message sur le forum.

      En suivant la solution de pepp, j'ai donc rajouté une méthode init() dans ma classe Data ainsi qu'une méthode deinit() appelées respectivement lors de la construction et de la destruction de mes objets dérivés. En appelant ces méthodes au sein des constructeurs et du destructeur de Icon par exemple, le this est bien du type dérivé et non du type de base Data, ce qui résoud le problème. C'est vrai cependant que ça retire un peu d'automatisme en forçant le développeur des classes dérivées à penser à appeler init() / deinit() dans chaque constructeur / destructeur mais à mon sens ça reste propre, vu qu'on ne rajoute pas de dépendance entre les classes dérivées et le catalogue (ou client dans mon cas d'école).
      • [^] # Re: Manque d'info

        Posté par  (site web personnel) . Évalué à 2.

        ATTENTION : en creusant le sujet, j'ai trouvé une limite à la solution de pepp. En effet, cela ne fonctionne qu'avec un seul niveau d'héritage. Dès que l'on veut dériver de Deriv (soit DerivDeriv), on retrouve le problème : l'appel à "pure" utilisera l'implémentation de Deriv, et pas DerivDeriv.


        En fait, je crois que le problème doit être rapproché de l'histoire du "smart pointer". En gros, on ne manipule pas directement les instances de la famille de Base, mais on passe systématiquement par des classes qui les encapsulent. De mémoire, on passe ici forcément par un template.

        Coté code, il faut reprendre le code d'un "smart pointer" et rajouter un appel à l'enregistrement dans Collection.

        Cette solution est doublement élégante (AMHA) :
        - on évite de faire le contournement et donc de répéter l'appel à init/deinit dans chaque fille,
        - le code d'enregistrement dans Collection est extrait de Base pour le mettre uniquement dans le template "smart pointer". On retire donc une dépendance entre Base et Collection.

        Je m'arretes là pour les explications. S'il y a besoin de détails, me faire signe.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.