2016-03-02 1 views
0

L'extrait de code "événement" suivant affiche l'erreur "appel de fonction virtuelle pure". Cependant, comme mentionné dans le titre, cela n'arrive que lors du déploiement sur DEBUG. Ce qui me rend curieux, c'est pourquoi cela fonctionne parfaitement sur RELEASE et pourquoi il se bloque même (sur DEBUG). Vous pouvez également voir l'extrait here.Erreur "appel de fonction virtuelle pure" sur le débogage SEULEMENT

#include <list> 
#include <iostream> 
#include <algorithm> 

// use base class to resolve the problem of how to put into collection objects of different types 
template <typename TPropertyType> 
struct PropertyChangedDelegateBase 
{ 
    virtual ~PropertyChangedDelegateBase(){}; 
    virtual void operator()(const TPropertyType& t) = 0; 
}; 

template <typename THandlerOwner, typename TPropertyType> 
struct PropertyChangedDelegate : public PropertyChangedDelegateBase<TPropertyType> 
{ 
    THandlerOwner* pHandlerOwner_; 

    typedef void (THandlerOwner::*TPropertyChangeHandler)(const TPropertyType&); 
    TPropertyChangeHandler handler_; 

public: 
    PropertyChangedDelegate(THandlerOwner* pHandlerOwner, TPropertyChangeHandler handler) : 
     pHandlerOwner_(pHandlerOwner), handler_(handler){} 

    void operator()(const TPropertyType& t) 
    { 
     (pHandlerOwner_->*handler_)(t); 
    } 
}; 

template<typename TPropertyType> 
class PropertyChangedEvent 
{ 
public: 
    virtual ~PropertyChangedEvent(){}; 

    void add(PropertyChangedDelegateBase<TPropertyType>* const d) 
    { 
     std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), d); 
     if(it != observers_.end()) 
      throw std::runtime_error("Observer already registered"); 

     observers_.push_back(d); 
    } 


    void remove(PropertyChangedDelegateBase<TPropertyType>* const d) 
    {  
     std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), d); 
     if(it != observers_.end()) 
      observers_.remove(d); 
    } 

    // notify 
    void operator()(const TPropertyType& newValue) 
    { 
     std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = observers_.begin(); 
     for(; it != observers_.end(); ++it) 
     { 
      (*it)->operator()(newValue); 
     } 
    } 

protected: 
    std::list<PropertyChangedDelegateBase<TPropertyType>* const> observers_; 
}; 

class PropertyOwner 
{ 
    int property1_; 
    float property2_; 

public: 
    PropertyChangedEvent<int> property1ChangedEvent; 
    PropertyChangedEvent<float> property2ChangedEvent; 

    PropertyOwner() : 
     property1_(0), 
     property2_(0.0f) 
    {} 

    int property1() const {return property1_;} 
    void property1(int n) 
    { 
     if(property1_ != n) 
     { 
      property1_ = n; 
      property1ChangedEvent(n); 
     } 
    } 

    float property2() const {return property2_;} 
    void property2(float n) 
    { 
     if(property2_ != n) 
     { 
      property2_ = n; 
      property2ChangedEvent(n); 
     } 
    } 
}; 

struct PropertyObserver 
{ 
    void OnPropertyChanged(const int& newValue) 
    { 
     std::cout << "PropertyObserver::OnPropertyChanged() -> new value is: " << newValue << std::endl; 
    } 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    PropertyOwner propertyOwner; 
    PropertyObserver propertyObserver; 

    // register observers 
    PropertyChangedDelegate<PropertyObserver, int> delegate(&propertyObserver, &PropertyObserver::OnPropertyChanged); 

    propertyOwner.property1ChangedEvent.add(&delegate); // Ok! 
    propertyOwner.property1ChangedEvent.add(&PropertyChangedDelegate<PropertyObserver, int>(&propertyObserver, &PropertyObserver::OnPropertyChanged)); // Error: Virtual pure function call (Debug only) 
    propertyOwner.property1(1); 

    return getchar(); 
} 
+0

'fonctionne parfaitement sur RELEASE et pourquoi il tombe même en panne (sur DEBUG)' C'est juste que la version release n'a pas cette vérification de l'exécution activée. Je ne suppose pas que c'est parfait. – Drop

+0

@Drop - il ne s'agit pas d'une vérification de l'exécution - l'appel virtuel pur ne se produit tout simplement pas puisque 'vtable' est toujours là. –

+0

@RudolfsBundulis Pourquoi pensez-vous que c'est "pas là" dans la version de débogage? – Drop

Répondre

0

Je suppose que l'erreur est mal choisi et que le problème est plus susceptible de faire avec la portée que les deuxièmes vie des délégués. Plus le déclarer à l'extérieur est plus facile à lire.

Il n'est généralement pas recommandé de contourner un objet créé sur la pile plutôt que par le tas. Une fois la déclaration d'article hors de portée, l'objet est généralement oublié.

+0

Que voulez-vous dire "déclarer à l'extérieur est plus facile à lire"? Aussi, pouvez-vous m'expliquer à propos de "contourner un objet créé sur la pile plutôt que le tas par référence est généralement une mauvaise idée.Une fois que la déclaration de l'article est hors de portée, l'objet est généralement oublié"? Je suis un peu perdu. –

+0

Déclarer votre délégué avant de le transmettre à la méthode qui l'enregistre en tant qu'observateur est plus facile à lire. C'est subjectif cependant mais donné quelque chose un nom est plus utile que non. Vous voulez regarder plus en profondeur dans la gestion de la mémoire en particulier "pile vs tas" –

+0

L'exemple montre à la fois déclarer à l'extérieur et "à l'intérieur", même si je voulais être sûr de ce qui se passe. Malgré cela, je gère maintenant les observateurs en interne dans la classe Event. J'ai trouvé cette approche bien meilleure parce que ces classes sont supposées être enveloppées dans un lib (je développe une lib qui analyse les fichiers TMX - voir mapeditor.org) et cela ne serait pas très pratique pour que les utilisateurs aient un tas de "dehors" "délégués dans leurs classes. Voici ce que j'ai fait: [link] (http://pastebin.com/pdYkzPAz) Qu'en penses-tu? –

0

Le problème général est que vous êtes lié à un temporaire qui est détruit et a donc un vtable vide et bien sûr il génère un appel virtuel pur lorsqu'il est appelé lors du changement de la propriété. Si vous ajoutez un dtor pour la classe de base ce qui est très facile à observer:

#include <list> 
#include <iostream> 
#include <algorithm> 

// use base class to resolve the problem of how to put into collection objects of different types 
template <typename TPropertyType> 
struct PropertyChangedDelegateBase 
{ 
    virtual ~PropertyChangedDelegateBase(){}; 
    virtual void operator()(const TPropertyType& t) = 0; 
}; 

template <typename THandlerOwner, typename TPropertyType> 
struct PropertyChangedDelegate : public PropertyChangedDelegateBase<TPropertyType> 
{ 
    THandlerOwner* pHandlerOwner_; 

    typedef void (THandlerOwner::*TPropertyChangeHandler)(const TPropertyType&); 
    TPropertyChangeHandler handler_; 

public: 
    PropertyChangedDelegate(THandlerOwner* pHandlerOwner, TPropertyChangeHandler handler) : 
     pHandlerOwner_(pHandlerOwner), handler_(handler) 
    { 
     std::cout << "0x" << std::hex << this << " created!" << std::endl; 
    } 

    void operator()(const TPropertyType& t) 
    { 
     (pHandlerOwner_->*handler_)(t); 
    } 

    ~PropertyChangedDelegate() 
    { 
     std::cout << "0x" << std::hex << this << " destroyed!" << std::endl; 
    } 
}; 

template<typename TPropertyType> 
class PropertyChangedEvent 
{ 
public: 
    virtual ~PropertyChangedEvent(){}; 

    void add(PropertyChangedDelegateBase<TPropertyType>* const d) 
    { 
     std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), d); 
     if (it != observers_.end()) 
      throw std::runtime_error("Observer already registered"); 

     observers_.push_back(d); 
    } 


    void remove(PropertyChangedDelegateBase<TPropertyType>* const d) 
    { 
     std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), d); 
     if (it != observers_.end()) 
      observers_.remove(d); 
    } 

    // notify 
    void operator()(const TPropertyType& newValue) 
    { 
     std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = observers_.begin(); 
     for (; it != observers_.end(); ++it) 
     { 
      std::cout << "Invoking 0x" << std::hex << *it << std::endl; 
      (*it)->operator()(newValue); 
     } 
    } 

protected: 
    std::list<PropertyChangedDelegateBase<TPropertyType>* const> observers_; 
}; 

class PropertyOwner 
{ 
    int property1_; 
    float property2_; 

public: 
    PropertyChangedEvent<int> property1ChangedEvent; 
    PropertyChangedEvent<float> property2ChangedEvent; 

    PropertyOwner() : 
     property1_(0), 
     property2_(0.0f) 
    {} 

    int property1() const { return property1_; } 
    void property1(int n) 
    { 
     if (property1_ != n) 
     { 
      property1_ = n; 
      property1ChangedEvent(n); 
     } 
    } 

    float property2() const { return property2_; } 
    void property2(float n) 
    { 
     if (property2_ != n) 
     { 
      property2_ = n; 
      property2ChangedEvent(n); 
     } 
    } 
}; 

struct PropertyObserver 
{ 
    void OnPropertyChanged(const int& newValue) 
    { 
     std::cout << "PropertyObserver::OnPropertyChanged() -> new value is: " << newValue << std::endl; 
    } 
}; 

int main(int argc, char* argv[]) 
{ 
    PropertyOwner propertyOwner; 
    PropertyObserver propertyObserver; 

    // register observers 
    PropertyChangedDelegate<PropertyObserver, int> delegate(&propertyObserver, &PropertyObserver::OnPropertyChanged); 

    propertyOwner.property1ChangedEvent.add(&delegate); // Ok! 
    propertyOwner.property1ChangedEvent.add(&PropertyChangedDelegate<PropertyObserver, int>(&propertyObserver, &PropertyObserver::OnPropertyChanged)); // Error: Virtual pure function call (Debug only) 
    propertyOwner.property1(1); 

    return getchar(); 
} 

Crashy crashy

Fondamentalement, vous êtes juste en cours d'exécution dans un comportement non défini - l'objet est détruit dans les deux cas, mais dans la version la vtable n'est pas détruit pour que vous vous en sortiez.

+0

J'ai adoré l'astuce "std :: hex". Merci! –

0

Ce:

propertyOwner.property1ChangedEvent.add(
    &PropertyChangedDelegate<PropertyObserver, int>(
    &propertyObserver, 
    &PropertyObserver::OnPropertyChanged) 
); 

Vous capturez un pointeur vers un objet temporaire PropertyChangedDelegate<PropertyObserver, int>. Le pointeur vers cet objet devient invalide dès que l'appel de fonction est terminé et que le signal temporaire est détruit. Le déréférencement de ce pointeur est un comportement indéfini. Dans votre programme, les relations de propriété de la mémoire sont essentielles et vous devez y réfléchir attentivement. Vous devez vous assurer que tous vos objets pointeurs survivront qui comptent sur eux, que ce soit manuellement:

PropertyChangedDelegate<PropertyObserver, int> delegate2 = { 
    &propertyObserver, 
    &PropertyObserver::OnPropertyChanged 
}; 

propertyOwner.property1ChangedEvent.add(&delegate2); 

ou en utilisant des pointeurs intelligents (std::unique_ptr<>, std::shared_ptr<>).

Un autre bug:

C++ 11 Compier compatible ne devrait pas vous permettre de faire ceci:

std::list<PropertyChangedDelegateBase<TPropertyType>* const> observers_; 

L'erreur que je suis arrivé avec Visual Studio 2015 est:

Le C++ Standard interdit les conteneurs d'éléments const car l'allocateur est mal formé. »

Voir: Does C++11 allow vector<const T>?

Bonus:

Votre style C++ ressemble un peu obsolète. Vous pourriez vouloir essayer déduction automatique de type:

for(auto it = observers_.begin(); it != observers_.end(); ++it) 
{ 
    (*it)->operator()(newValue); 
} 

ou, mieux, ont varié pour les boucles:

for(auto observer : observers) 
{ 
    observer(newValue); 
} 

Vous pouvez jeter un oeil à:

+0

Donc cet objet "vit" aussi longtemps que la fonction ".add (& ...)" est en cours d'exécution? C'est ce que je pensais mais, cet objet est poussé dans un vecteur. Ne devrait-il pas vivre aussi longtemps que la classe ".add (& ...)" de la classe de vie (PropertyChangedEvent)? Je suis conscient du style dont vous parlez, je l'ai corrigé dans mon implémentation actuelle. Cet exemple a été copié à partir d'ici: [link] (http://stackoverflow.com/a/10435059/5446775). –

+0

@YvesHenri Une erreur se produit dans 'propertyOwner.property1 (1);', pas dans la ligne que vous avez commentée. Vous poussez des pointeurs (une adresse entière de la valeur) dans votre conteneur. L'acquisition d'un pointeur brut n'empêche pas les objets d'être détruits. Il y a aussi un autre bug avec les pointeurs 'const' (je l'ai ajouté à la réponse) – Drop