由于类切片导致的未处理异常

时间:2011-07-28 17:35:09

标签: c++ object-slicing

我在下面的注明行中得到一个未处理的异常读取位置0x00000008(读取NULL值),包含导致错误的相关方法(在下面的示例中继续):

活动方法:

Event::Event(Event::EVENTTYPE type) : eventType(type) { }

KeyEvent方法:

class KeyboardKeyEvent : public Event {
public:
    //...
    int GetKey() const;
protected:
//...
};

int KeyboardKeyEvent::GetKey() const {
    return this->_scancode; //Errors out here. "this" returns 0x000000
}
KeyboardKeyEvent::KeyboardKeyEvent(int key, Event::EVENTTYPE type) : Event(type), _scancode(key) { }

KeyDownEvent方法:

KeyboardKeyDownEvent::KeyboardKeyDownEvent(int scancode) : KeyboardKeyEvent(scancode, Event::KEYBOARD_KEYDOWN) { }

事件处理程序方法:

bool EventHandler::EnqueueEvent(Event* event) {
    if(event == NULL) return false;
    try {
        this->_eventQueue.push(event);
    } catch (...) {
        return false;
    }
    return true;
}

Event* EventHandler::DequeueEvent() {
    if(this->_eventQueue.empty() == false) {
        Event* result = new Event(*this->_eventQueue.front());
        delete this->_eventQueue.front();
        this->_eventQueue.pop();
        return result;
    }
    return NULL;
}

主循环序列:

if(_eh->HasEvents()) {
    Event* nxtEvent = _eh->DequeueEvent();
    switch(nxtEvent->GetType()) {
        case Event::KEYBOARD_KEYDOWN:
            allegro_message("You pressed the %d key!", dynamic_cast<KeyboardKeyDownEvent*>(nxtEvent)->GetKey());
            break;
        default:
            /* DO NOTHING */;
    }
    delete nxtEvent;
    nxtEvent = NULL;
}

我知道这是一个切片问题我只是不明白它为什么会发生或者如何解决它(实际上,现在我想到它,它可能是“无法转换为请求类型”错误)。当我单步执行程序_scancode时,所有这些都是适当的值,但第dynamic_cast<KeyboardKeyDownEvent*>(nxtEvent)->GetKey()行运行它会引发错误。双重转换为dynamic_cast<KeyboardKeyDownEvent*>(dynamic_cast<KeyboardKeyEvent*>(nxtEvent))->GetKey()失败并出现相同的错误。

修改

经过一些调整后,这种变体非常有效:

if(_eh->HasEvents()) {
    switch(_eh->PeekEvent()->GetType()) {
    case Event::KEYBOARD_KEYDOWN:
        allegro_message("You pressed the %s key!", scancode_to_name(dynamic_cast<KeyboardKeyDownEvent*>(_eh->PeekEvent())->GetKey()));
        break;
    case Event::MOUSE_BUTTONDOWN:{
        Mouse::BUTTONS btn = dynamic_cast<MouseButtonDownEvent*>(_eh->PeekEvent())->GetButton();
        if(btn == Mouse::BUTTON2) {
            allegro_message("You pressed the %d button!", dynamic_cast<MouseButtonDownEvent*>(_eh->PeekEvent())->GetButton());
        }
                                 }
        break;
        default:
            /* DO NOTHING */;
    }
}

4 个答案:

答案 0 :(得分:1)

避免切片的一个解决方案是使基类的析构函数为虚拟,所以在你的情况下你可以使~Event()虚拟:

class Event
{
 public:
    //...    
    virtual ~Event() {}
};

顺便说一下,我想知道你为什么要这样做:

//YOUR CODE : its causing the problem!
Event* EventHandler::DequeueEvent() {
    if(this->_eventQueue.empty() == false) {
        Event* result = new Event(*this->_eventQueue.front()); // WHY?
        delete this->_eventQueue.front();  //WHY?
        this->_eventQueue.pop();
        return result;
    }
    return NULL;
}

你为什么不这样做:

//Use it. Because it should not cause that probem
Event* EventHandler::DequeueEvent() {
    if(this->_eventQueue.empty() == false) {
        Event* result = this->_eventQueue.front();
        this->_eventQueue.pop();
        return result;
    }
    return NULL;
}

答案 1 :(得分:1)

Event* EventHandler::DequeueEvent()你有线 Event* result = new Event(*this->_eventQueue.front());此处发生切片。 您可以执行以下操作:

class Event {
 public:
 virtual Event* clone() {
  // create a new instance and copy all the fields
}  

}

然后在派生类中覆盖clone(),例如

class KeyboardKeyEvent :public Event {
 public: 
 ... 
 virtual KeyboardKeyEvent* clone(); // note - it returns different type
}

然后更改Event* EventHandler::DequeueEvent():   Event* result = (*this->_eventQueue.front()).clone();

答案 2 :(得分:1)

您的DequeueEvent方法将始终返回一个Event对象,而不是您期望的任何子类。

    Event* result = new Event(*this->_eventQueue.front());

您的Dequeue事件应该返回它正在缓存的实际引用,或者您的基类Event类需要提供某种虚拟副本操作来提供真正的克隆。

答案 3 :(得分:1)

为什么在从队列中删除事件时复制事件?这就是切片的作用,因为你正在构建基类。相反,将队列中的指针返回给用户。

如上所述,Event应该有一个虚拟的~Event(),以便事件的接收者可以正确地删除它。否则,具体的类析构函数将无法正常运行。

Event* EventHandler::DequeueEvent() {
    if(this->_eventQueue.empty() == false) {
        Event* result = this->_eventQueue.front();
        this->_eventQueue.pop();
        return result;
    }
    return NULL;
}