2016-03-02 152 views
0

以下「事件」代碼片段顯示「純虛函數調用」錯誤。但是,正如標題中提到的那樣,只有在DEBUG上部署時纔會發生。令我感到好奇的是,爲什麼它在RELEASE上完美地工作,以及爲什麼它甚至會崩潰(在DEBUG上)。 或者,您可以看到片段here僅在調試時發生「純虛函數調用」錯誤

#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

'在RELEASE上完美地工作,爲什麼它甚至崩潰(在DEBUG上)'只是發佈版本沒有啓用這個運行時檢查。不會認爲它是完美的。 – Drop

+0

@Drop - 這不是關於運行時檢查 - 純虛擬調用根本不會發生,因爲'vtable'仍然存在。 –

+0

@RudolfsBundulis爲什麼你認爲它在調試版本中「不存在」? – Drop

回答

0

我會認爲錯誤是錯誤的,而且問題更可能與第二個代表所處的範圍有關。 Plus在外面宣佈它更容易閱讀。

傳遞堆棧上創建的對象而不是堆引用通常是一個壞主意。一旦項目聲明超出範圍,對象通常會被遺忘。

+0

你是什麼意思「在外面宣稱更容易閱讀」?另外,你能否告訴我,「通過引用傳遞堆棧中創建的對象而不是堆引用通常是一個壞主意。一旦項目聲明超出範圍,對象通常會被遺忘」?我有點失落。 –

+0

在將它傳遞到將其註冊爲觀察者的方法之前聲明您的委託更易於閱讀。儘管這是主觀的,但是給出一個名稱比沒有更有幫助。你想更深入地瞭解內存管理,尤其是「堆棧vs堆」 –

+0

該示例顯示了聲明外部和「內部」,但我想確定發生了什麼事情。儘管如此,我現在在Event類的內部管理觀察者。我發現這種方法要好得多,因爲這些類應該被封裝在一個庫中(我正在開發一個解析TMX文件的庫 - 請參閱mapeditor.org),並且它不會讓用戶擁有一堆「外部「代表們上課。以下是我所做的:[鏈接](http://pastebin.com/pdYkzPAz) 您怎麼看? –

0

一般問題是,你綁定到一個臨時被破壞,因此有一個空的vtable,當它調用屬性的變化時,它會產生一個純虛擬調用。如果添加了析構函數的基類,這是很容易觀察到:

#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

基本上你只是運行到不確定的行爲 - 對象被銷燬在這兩種情況下,但在發佈的vtable沒有被破壞,所以你過得去。

+0

我喜歡「std :: hex」技巧。謝謝! –

0

此:

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

要捕獲一個指向臨時對象PropertyChangedDelegate<PropertyObserver, int>。一旦函數調用結束並且臨時被銷燬,指向該對象的指針就會失效。解引用這個指針是未定義的行爲。

在你的程序中,內存所有權的關係非常重要,你應該認真思考它們。 你需要確保你的所有指針活得比對其依賴的對象,無論是手動:

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

propertyOwner.property1ChangedEvent.add(&delegate2); 

或通過使用智能指針(std::unique_ptr<>std::shared_ptr<>)。

另一個錯誤:

C++ 11兼容compier不應該讓你這樣做:

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

我與Visual Studio 2015年得到的錯誤是:

的C++標準禁止const元素的容器,因爲分配器是不合格的.`

請參見:Does C++11 allow vector<const T>?

獎勵:

你的C++風格看起來頗有幾分過時。 你可能想嘗試自動型扣:

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

,或者更好,遠程for循環:

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

你可能想看一看到:

+0

只要「.add(&...)」函數正在執行,那麼這個對象是否「生活」?多數民衆贊成我認爲,但是,該對象被推入矢量。只要「.add(&...)」函數的類存在(PropertyChangedEvent),它就不應該存在? 我知道你所談論的風格,我已經在我目前的實施中修復了它。該示例從這裏複製:[鏈接](http://stackoverflow.com/a/10435059/5446775)。 –

+0

@YvesHenri'propertyOwner.property1(1);'中出現錯誤,而不是你所評論的那一行。您在容器中推送指針(值的整數地址)。獲取原始指針不會阻止對象被破壞。另外還有一個'const'指針的錯誤(我將它添加到答案中) – Drop