IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Appel de fonction virtuelle pure


Sujet :

Langage C++

  1. #1
    Membre confirmé Avatar de Darktib
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 66
    Points : 601
    Points
    601
    Par défaut Appel de fonction virtuelle pure
    Bonjour,

    J'ai deux classes, dont l'une gère l'autre (code source ci-après). Il se trouve que lors de la destruction de la première classe, j'ai un appel à une fonction virtuelle pure. Je tiens à préciser que j'ai cherché sur internet un solution éventuelle à mon problème, mais je n'ai rien trouvé qui corresponde vraiment à mon problème.

    Donc voici le code:

    action_manager.h:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    #ifndef _ACTIONS_LIST_INCLUDED_
    #define _ACTIONS_LIST_INCLUDED_
    #include <list>
    #include "systemLists.h"
    #include "..\devicesStruct.h"
     
    class Entity;
    class Action;
     
    class ActionsList
    {
    	public:
    		//! Constructeur et destructeur
    		ActionsList(SDevices* devs, Entity* owner);
    		~ActionsList();
     
    		//! Gestion des actions
    		void addAction(ENUM_ACTION_INDEXES_LIST action);
    		void popAction();
    		void clear();
     
    		//! Mise à jour
    		void execute(float deltaT);
     
    		//! Informations
    		int getActionCount();
    		SDevices* getDevices();
     
    	private:
    		void activateFirstAction();
    		std::list<Action*> actList;
    		Entity* actionsOwner;
    		int totalActions;
    		SDevices* devices;
    };
     
    #endif // _ACTIONS_LIST_INCLUDED_
    action_manager.cpp:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    #include "ActionsBase.h"
    #include "ActionsList.h"
    #include "..\Entity\Entity.h"
     
    //! Actions de base
    void Action::setEntityAnimation(unsigned int start, unsigned int end)
    {
    	float num = object->getSceneNode()->getFrameNr();
    	object->setAnimationRange(start,end);
    	if(num>=start && num<=end) object->getSceneNode()->setCurrentFrame(num);
    }
     
    //! Constructeur et destructeur
    ActionsList::ActionsList(SDevices* devs, Entity* owner)
    :actionsOwner(owner),totalActions(0),devices(devs)
    {
    }
    ActionsList::~ActionsList()
    {
    	printf("Clearing...\n");
    	clear();
    	printf("Cleared !\n");
    }
     
    //! Gestion des actions
    void ActionsList::addAction(ENUM_ACTION_INDEXES_LIST action)
    {
    	if(baseActions[(int)action])
    	{
    		Action* act = baseActions[(int)action]->cloneType();
    		act->manager = this;
    		act->object = actionsOwner;
    		actList.push_back(act);
    		totalActions++;
    		act->OnActionCreation();
    		if(totalActions==1)
    			activateFirstAction();
    	}
    }
    void ActionsList::popAction()
    {
    	if(actList.begin() != actList.end())
    	{
    		Action* act = *actList.begin();
    		act->OnActionDeletion();
    		delete act;
    		actList.erase(actList.begin());
    		totalActions--;
    		activateFirstAction();
    	}
    }
    void ActionsList::clear()
    {
    	while(actList.begin() != actList.end())
    		popAction();
    }
     
    //! Mise à jour
    void ActionsList::execute(float deltaT)
    {
    	if(actList.begin() != actList.end())
    		(*(actList.begin()))->exec(deltaT);
    	else addAction(EAIL_IDLE);
    }
     
    //! Informations
    int ActionsList::getActionCount()
    {
    	return totalActions;
    }
    SDevices* ActionsList::getDevices()
    {
    	return devices;
    }
     
    //! Privé
    void ActionsList::activateFirstAction()
    {
    	if(actList.begin() != actList.end())
    	{
    			Action* act = *actList.begin();
    // Voici la fonction qui plante : 
    			act->OnActionActivation();
    	}
    }
    L'appel à la fonction OnActionActivation plante uniquement lorsque l'actionlist est détruite. J'ai fait le test, en cours d'exécution + de 5000 appels de cette fonction ne font absolument aucune erreur...
    Par contre, quand on l'appelle depuis le destructeur d'ActionList (via clear() -> popAction() ), ca plante dès la première exécution...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
     
    #include "..\devicesStruct.h"
    #include "systemLists.h"
    #include "ActionsList.h"
    #include "..\type.h"
    #include <vector>
     
    class Entity;
     
    struct Action
    {
    	//! La classe dont dépendent les actions
    	ActionsList* manager;
    	Entity* object;
     
    	//! Les données de l'action
    	std::vector<VoidValue> data;
     
    	//! Destructeur virtuel
    	virtual ~Action() {}
     
    	//! Slots
    	virtual void OnActionCreation() =0;
    	virtual void OnActionActivation() =0;
    	virtual void OnActionDeletion() =0;
     
    	//! Fonctions principales
    	virtual void exec(float deltaT) =0;
    	virtual Action* cloneType() =0;
    	virtual void escapeIfOtherActionsInList()
    	{ if(manager->getActionCount()>1) manager->popAction(); }
    	virtual void finish()
    	{ manager->popAction(); }
    	virtual void setEntityAnimation(unsigned int start, unsigned int end);
     
    	//! RTTI
    	virtual ENUM_ACTION_INDEXES_LIST getType() =0;
    };
    Toutes les fonctions sont bien réimplémentées dans les classes filles...
    Et j'obtient l'erreur meme si je met
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    	virtual void OnActionActivation() {printf("Act!\n");}
    au lieu de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    	virtual void OnActionActivation() =0;
    dans la classe de base...


    Quelqu'un aurait il une idée ?

    Merci d'avance.

  2. #2
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Bonjour et bienvenu,
    Je dois avouer que je ne vois rien qui saute aux yeux expliquant ton pb.
    As tu mis un point d'arrêt dans clear pour voir les différents pointeurs retournés ?
    J'aurais rajouté une trace dans les destructeur de Action pour voir s'ils ne sont pas détruit 2 fois ?


    Ensuite, quelques remarques :
    1/ tu peux utiliser pop_front qui est plus parlant que erase dans ton cas ;
    2/ J'aurais fais le delete après le erase/pop_front
    3/ C'est bizarre d'avoir une méthode 'popAction' alors que ta liste se comporte comme une FIFO et non comme une stack (qui existe en std par ailleurs).
    4/ Pourquoi ne pas utiliser des pointeurs intelligents dans la liste ?

  3. #3
    Membre confirmé Avatar de Darktib
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 66
    Points : 601
    Points
    601
    Par défaut
    J'ai testé avec des pointeurs, et ils sont tous supprimés une fois. De plus, en mettant un message dans le pseudo destructeur des actions (OnActionDeletion, le vrai destructeur ne sert a rien car il n'y a rien à supprimer), je vois que chaque destructeur est appelé une fois. Si l'objet était supprimé deux fois, j'aurais eu une violation d'accès mémoire, plus facile à corriger...

    Sinon pour les remarques:
    1/ Merci de la suggestion, je vais changer ca.
    2/ Idem
    4/ Les Actions ne sont utilisées que par cette classe, aucune autre classe ne peut les utiliser. De plus, toute action crée n'appartient qu'a la même actionslist, et doit etre détruite si jamais la liste n'en a plus besoin. C'est pourquoi je pense que des pointeurs intelligents ne sont pas très utiles ici.

    J'ai fait d'autres tests, en sortant l'appel de clear() du destructeur. C'est l'entité qui possède la liste d'actions qui appelle le clear() de la liste, puis la supprime après. Résultat : aucun changement...

  4. #4
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Il y a peut être un problème d'intégrité de ta liste ou d'un de ses objets (un écrasement ?). As-tu pisté de ce côté ?

  5. #5
    Membre confirmé Avatar de Darktib
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 66
    Points : 601
    Points
    601
    Par défaut
    Je vais chercher, mais ca serait bizarre...
    En effet, ce crash n'est pas aléatoire.

    Sinon j'ai appliqué tes deux premières suggestions, aucun changement a part un code plus lisible (c'est déjà ca)

    edit : Les pointeurs sont apparemment bons, j'ai testé avec la fonction getType() des Actions, et là aucun problème.

  6. #6
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Le problème - si t'as un écrasement - c'est qu'il peut très bien y avoir des choses qui se comportent bien et d'autres non. Au vu du code que tu fournis, je ne vois pas de raison évidente pour qu'il y aie un problème spécifiquement sur les méthodes virtuelles pures. C'est pour ça que j'ai tendance à penser que le bug est ailleurs...

    [EDIT] : peux-tu poser le code de la première action détruite ?

  7. #7
    Membre confirmé Avatar de Darktib
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 66
    Points : 601
    Points
    601
    Par défaut
    Mais comment fait on alors pour voir s'il y a un écrasement ?
    (Et le corriger ?)

  8. #8
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Soit tu as un outils (valgring, purify, BoundsChecker, Insure++) soit tu bricoles à la main. Un truc rapide peut être de mettre un marqueur (eg '0xABCDEF') avant et après tes objets et vérifier s'ils sont toujours présents (cf redéfinition de l'opérateur new).

  9. #9
    Membre confirmé Avatar de Darktib
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 66
    Points : 601
    Points
    601
    Par défaut
    Je vais voir pour les outils, perso je sais déjà que je ne pourrais pas utiliser valgrind car il est pour Linux.

    Sinon j'ai trouvé une solution contournant le problème : utiliser la fonction clear() suivante:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void ActionsList::clear()
    {
    	while(actList.begin() != actList.end())
    	{
    		Action* act = *actList.begin();
    		act->OnActionDeletion();
    		actList.pop_front();
    		delete act;
    	}
    	totalActions = 0;
    }
    Plus de crashs, mais je ne comprend toujours pas le bug...

  10. #10
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par Darktib Voir le message
    Plus de crashs, mais je ne comprend toujours pas le bug...
    Il y a une différence : tu ne fais plus l'appel à activateFirstAction !
    [EDIT] : ton code plante qu'en release ou aussi en debug ?

  11. #11
    Membre confirmé Avatar de Darktib
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 66
    Points : 601
    Points
    601
    Par défaut
    J'avais compris qu'en n'appelant plus activateFirstAction ca planterais plus, et c'est pourquoi j'ai changé la fonction clear()
    Mais je ne vois toujours pas pourquoi il y aurai corruption de mémoire.

    Je viens de me souvenir que j'avais eu un bug quasi identique il y a en gros une année, sauf que la fonction était juste virtuelle (et pas virtuelle pure), que ca faisait une segfault et que c'est parti tout seul...

    Je compile avec gcc 3.4.5, même si j'ai visual studio (je tente cependant d'utiliser le compilateur de vs avec Code::Blocks);

    Sinon ca plantait en debug et en release.

  12. #12
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    J'ai beau regarder activateActionFirst, je vois pas trop où pourrait être le problème. A moins qu'il y aie une optimisation de la mort qui cacherait le test begin/end puis le begin suivant à cause d'un inline (d'où la question debug/release), mais ça m'étonnerait.
    Peut toujours essayer :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
     
    void ActionsList::clear()
    {
    	while(actList.begin() != actList.end())
    		popAction();
    }
    void ActionsList::popAction()
    {
    	if(!actList.empty())
    	{
    		Action* act = *actList.begin();
          actList.pop_back();
    		act->OnActionDeletion();
    		delete act;
    		totalActions--;
    		activateFirstAction();
    	}
    }
     
    void ActionsList::activateFirstAction()
    {
    	if(!actList.empty())
    	{
    			Action* act = *actList.begin();
    			act->OnActionActivation();
    	}
    }
    Mais je pense pas que ça change grand chose...
    As-tu mis un point d'arrêt à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    act->OnActionActivation();
    puis rentrée dans la fonction avec le debugger pour vérifier l'état interne de act ?

  13. #13
    Membre confirmé Avatar de Darktib
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 66
    Points : 601
    Points
    601
    Par défaut
    Vérifier que la liste n'est pas vide ?
    Le test avec les itérateurs est bon, car lorsqu'il n'y a une seule action dans la liste, elle est supprimée et donc la liste est vide lorsque activateFirstAction() est appelée. En effet, ca ne plante que lorsqu'il y a au moins deux actions dans la liste avec ma méthode (comme avec la tienne)...

    Je vais voir pour le point d'arrêt, mais j'ai de gros doutes... en effet le programme quitte tout suite, du coup ca arrête le débuggueur...

  14. #14
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par Darktib Voir le message
    Vérifier que la liste n'est pas vide ?
    Le test avec les itérateurs est bon, car lorsqu'il n'y a une seule action dans la liste, elle est supprimée et donc la liste est vide lorsque activateFirstAction() est appelée. En effet, ca ne plante que lorsqu'il y a au moins deux actions dans la liste avec ma méthode (comme avec la tienne)...

    Je vais voir pour le point d'arrêt, mais j'ai de gros doutes... en effet le programme quitte tout suite, du coup ca arrête le débuggueur...
    actList.begin() != actList.end() et !actList.empty() pour moi c'est équivalent - si ce n'est que la seconde (avec empty) dit explicitement à quoi sert le test...
    C'est pourquoi je ne crois pas que cela change grand chose à ton problème.

    Le code que tu présentes au début est strictement le même que tu as ? Il n'y a pas dans ton code un ; qui traine derrière le if ou quelque chose d'autre (essayons d'autres pistes) ?

  15. #15
    Membre confirmé Avatar de Darktib
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 66
    Points : 601
    Points
    601
    Par défaut
    Normalement il n'y a pas de différences...
    Je pense de plus en plus a un bug du compilo. En effet gcc m'a pas l'air d'etre extrêmement évolué... d'autant plus qu'il est vieux...

    Je vais voir si en désactivant la prise en charge du SEH ca améliore les choses (c'est pas une prise en charge native du compilo, c'est un code trouvé ici:http://www.programmingunlimited.net/...page=mingw-seh)

    edit: ca change rien. Cette erreur est plus que pénible, parce que du coup ca plante de partout... Le problème de Purify et de Insure++, c'est qu'ils sont payants, et que je ne peux pas les acheter.


    edit2: j'ai testé avec visual studio. Donc le bug vient apparement pas du compilateur, vu qu'il se produit aussi avec visual studio. J'essaie d'en savoir plus.

    edit3: j'ai bien fait de passer à VCC, le débugage est franchement efficace.
    Apparemment, on a tout faux sur la fonction virtuelle appelée. C'est pas OnActionActivation qui plante, mais la fonction setAnimationRange qui est appelée dans OnActionAnimation. En effet, cette fonction récupère un pointeur sur un objet qui en cours de destruction.
    Juste une question : si l'on dispose d'une classe de base A et d'une classe dérivée de A nommée B.
    Lors de la destruction de B, quel objet est détruit en premier? A ou B (apparemment ca serait B).

  16. #16
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    En général, ce comportement apparaît quand on appelle une fonction virtuelle depuis un constructeur/destructeur. Il ne faut pas oublier qu'en effet, dans ces fonctions, le type dynamique de l'objet se trouve être celui de l'objet en cours, et non celui de l'objet final, et donc un appel de fonction virtuelle peut aboutir sur une fonction virtuelle pure.

    Exemple (code non testé) pour reproduire ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    struct A 
    {
      virtual void f()=0;
      virtual ~A() {f();}
    };
     
    struct B : A
    {
      virtual void f() {}
    };
    Après, je n'ai pas vu ce problème dans le code que tu nous as montré, mais tu ne nous as pas tout montré...
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  17. #17
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par Darktib Voir le message
    Juste une question : si l'on dispose d'une classe de base A et d'une classe dérivée de A nommée B.
    Lors de la destruction de B, quel objet est détruit en premier? A ou B (apparemment ca serait B).
    L'ordre de destruction est l'ordre inverse de la construction :
    1. destructeur de B ;
    2. destructeur des membres de B ;
    3. destructeur de la classe de base.

    C'était alors comme disait Loic : appel d'une membre virtuel pure dans le destructeur de A ?

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 4
    Dernier message: 11/10/2013, 12h08
  2. Réponses: 15
    Dernier message: 07/06/2010, 20h25
  3. Fonction appelant une fonction virtuelle pure
    Par oodini dans le forum C++
    Réponses: 12
    Dernier message: 19/09/2008, 08h24
  4. Réponses: 2
    Dernier message: 05/03/2006, 19h29
  5. Compilation avec des fonctions virtuel pure
    Par vanitom dans le forum C++
    Réponses: 4
    Dernier message: 16/12/2005, 14h37

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo