2016-03-02 2 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

Я хотел бы предположить, что ошибка и неправильно, что проблема, скорее всего, делать с размахом, что второй делегат жизни. Плюс объявить его снаружи легче читать.

Прохождение вокруг объекта, созданного в стеке, а не кучи по ссылке, как правило, плохая идея. Когда объявление элемента выходит за пределы области, объект обычно забывается.

+0

Что значит «объявить его снаружи проще читать»? Кроме того, вы можете рассказать мне о «прохождении объекта, созданного в стеке, а не о куче по ссылке, как правило, это плохая идея. Когда объявление элемента выходит за пределы области, объект обычно забыт о«? Я немного потерян. –

+0

Объявление вашего делегата перед его передачей в метод, который регистрирует его как наблюдателя, легче читать. Это субъективно, но, однако, что-то имя более полезно, чем нет. Вы хотите более подробно изучить управление памятью, в частности, «стек против кучи» –

+0

В этом примере показано, как объявить внешний и «внутри», хотя я хотел убедиться, что происходит. Несмотря на это, я теперь управляю наблюдателями внутри класса Event. Я нашел этот подход намного лучше, потому что эти классы должны быть завернуты в lib (я разрабатываю lib, который анализирует файлы TMX - см. Mapeditor.org), и было бы не очень удобно, чтобы пользователи имели кучу «вне «делегаты в своих классах. Вот что я сделал: [link] (http://pastebin.com/pdYkzPAz) Как вы думаете? –

0

Общая проблема заключается в том, что вы привязываетесь к временному, который уничтожается и, следовательно, имеет пустой vtable и, конечно же, он генерирует чистый виртуальный вызов при вызове при изменении свойства. Если добавить dtor для базового класса это довольно легко заметить:

#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(auto observer : observers) 
{ 
    observer(newValue); 
} 

Вы можете захотеть взглянуть на:

+0

Так ли этот объект «живет» до тех пор, пока выполняется функция «.add (& ...)»? То, что я думал, но, этот объект вставляется в вектор. Должен ли он жить до тех пор, пока класс функции «.add (& ...)» живет (PropertyChangedEvent)? Я знаю о стиле, о котором вы говорите, я исправил его в моей текущей реализации. Этот пример был скопирован здесь: [link] (http://stackoverflow.com/a/10435059/5446775). –

+0

@YvesHenri Ошибка происходит в 'propertyOwner.property1 (1);', а не в строке, которую вы прокомментировали. Вы нажимаете указатели (целочисленный адрес значения) в своем контейнере. Приобретение необработанного указателя не препятствует уничтожению объектов. Также есть еще одна ошибка с указателями 'const' (я добавил ее к ответу) – Drop

Смежные вопросы