1

我有以下示例代码:

#include <iostream>
#include <string>

using namespace std;

class Event
{
public:
    string type;
    string source;
};

class KeyEvent : public Event
{
public:
    string key;
    string modifier;
};

class MouseEvent : public Event
{
public:
    string button;
    int x;
    int y;
};

void handleEvent(KeyEvent e)
{
    if(e.key == "ENTER")
        cout << "Hello world! The Enter key was pressed ;)" << endl;
}

Event generateEvent()
{
    KeyEvent e;
    e.type = "KEYBOARD_EVENT";
    e.source = "Keyboard0";
    e.key = "SPACEBAR";
    e.modifier = "none";

    return e;
}

int main()
{
    KeyEvent e = generateEvent();

    return 0;
}

我无法编译它,G++ 会抛出一个错误:

main.cpp: In function 'int main()':
main.cpp:47:29: error: conversion from 'Event' to non-scalar type 'KeyEvent' requested

我知道 C++ 大师的错误很明显,但我不明白为什么我不能从基类对象转换为派生对象。有人可以建议我解决我遇到的问题吗?谢谢你的建议

4

5 回答 5

2

您的函数generateEvent执行以下操作:

  • 创建一个KeyEvent
  • 通过复制和切片将对象转换(向上转换)为事件
  • 返回该事件对象

然后,您尝试获取该Event对象副本并再次将其放入 aKeyEvent中。

您正在尝试使用多态性,但实际上只是切片。考虑(谨慎!)动态分配:

boost::shared_ptr<Event> generateEvent() {
    KeyEvent* e = new KeyEvent;
    e->type = "KEYBOARD_EVENT";
    e->source = "Keyboard0";
    e->key = "SPACEBAR";
    e->modifier = "none";

    return boost::shared_ptr<Event>(static_cast<Event*>(e));
}

int main() {
    boost::shared_ptr<Event> e = generateEvent();
    // you can now use dynamic_cast and/or virtual
    // function calls to treat e as a pointer-to-KeyEvent
}

另请注意,这return 0;在入口点函数中是隐含的。

于 2011-01-02T22:36:08.487 回答
1

generateEvent函数通过Event值(而不是通过指针或引用)传递。这意味着KeyEvent您尝试传递的对象将被“切片”为一个Event对象,并且keyandmodifier字段将被丢弃。

错误消息不是最有帮助的,但编译器试图说的是它无法将Event值转换为KeyEvent值。转换需要综合字段的默认值,因为当对象按值返回KeyEvent时,原始字段被切掉了。Event

您可以通过动态分配 a KeyEventingenerateEventgenerateEvent返回 aEvent*或通过引用generateEvent接受 a来避免此错误。KeyEvent通过使用指针或引用,您可以避免对象切片问题。

于 2011-01-02T15:43:12.167 回答
1

这条线应该做什么:

KeyEvent e = generateEvent();

是调用一个构造函数,KeyEvent它接受一个Event对象或一个对象的引用。但是,您的KeyEvent类没有这样的构造函数,因此您的编译器告诉您它不能KeyEventEvent对象中生成(“错误:从 'Event' 转换为非标量类型 'KeyEvent' 请求”)。

您可以使用以下代码:

#include <iostream>
#include <memory>
#include <string>

using namespace std;

class Event
{
public:
    string type;
    string source;

    virtual ~Event() { }
};

class KeyEvent : public Event
{
public:
    string key;
    string modifier;

    virtual ~KeyEvent() { }
};

class MouseEvent : public Event
{
public:
    string button;
    int x;
    int y;

    virtual ~MouseEvent() { }
};

void handleEvent(const KeyEvent& e)
{
    if(e.key == "SPACEBAR")
        cout << "Hello world! The Space key was pressed ;)" << endl;
}

auto_ptr<Event> generateEvent()
{
    auto_ptr<KeyEvent> ret(new KeyEvent);
    ret->type = "KEYBOARD_EVENT";
    ret->source = "Keyboard0";
    ret->key = "SPACEBAR";
    ret->modifier = "none";

    return auto_ptr<Event>(ret.release());
}

int main()
{
    auto_ptr<Event> pEvent = generateEvent();
    KeyEvent *pKeyEvent = dynamic_cast<KeyEvent*>(pEvent.get());
    if (pKeyEvent) {
        handleEvent(*pKeyEvent);
    }

    return 0;
}

http://codepad.org/DcBi7jxq

于 2011-01-02T15:52:29.540 回答
1

自动转换 from Eventto是不可能KeyEvent的,编译器不知道要放入什么,例如KeyEvent::key

请注意建议的cast解决方案,因为没有类型检查,一旦收到不同类型的事件(是EventaKeyEvent还是 a MouseEvent?),您就会遇到问题。常见的解决方案是添加类型 ID 或使用虚拟函数(更安全,但有时不太直接)。

于 2011-01-02T15:53:46.890 回答
1

您遇到的问题是基于这样一个事实,虽然实际上generateEvent确实创建了 a KeyEvent,但这只有程序员(您)知道。编译器所知道的是generateEvent返回 a Event,在一般情况下它实际上不是KeyEventa 。因此,编译器抱怨您正在将正式(如函数定义所述)不是 a 的东西KeyEvent视为 a KeyEvent

最有可能main的是,如果事件实际上是一个KeyEvent. 这是一种常见的情况,您尝试做的事情并没有错。你只需要以不同的方式去做。

在这种情况下,我们要做的是对事件“执行动作 X”,其中“动作 X”是不同的,具体取决于事件是 aKeyEvent还是其他。这样做的方法是使用虚函数,如下所示:

class Event
{
public:
    string type;
    string source;

    virtual void PerformActionX();
};

进而:

int main()
{
    Event e = generateEvent();
    e.PerformActionX();

    return 0;
}

每个派生类的实现PerformActionX会有所不同。该方法也可以是纯虚拟的或不是纯虚拟的。所有这一切都取决于您究竟想要做什么。

最后一点,有一些场景(以及这个问题的一些答案)建议尝试“发现”究竟是什么类型的事件e,然后强制转换为该类型并执行一些明确的操作(例如访问keya 的成员KeyEvent)如果是特定类型。这种处理称为类型切换,通常是个坏主意。虽然可能存在需要类型切换的有效场景,但最好以面向对象语言(使用虚函数)的方式处理此类情况。先学会按规矩办事,以后再违反规矩。

于 2011-01-02T16:15:29.270 回答