基类 - > C ++中的派生类和反之亦然转换

时间:2011-01-02 15:30:59

标签: c++ casting derived-class base-class

我有以下示例代码:

#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 ++大师的错误是显而易见的,但我无法理解为什么我不能从基类对象转换为派生类。有人可以建议我解决我遇到的问题吗? Thx in advice

5 个答案:

答案 0 :(得分:1)

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

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

您可以通过在KeyEvent中动态分配generateEvent,让generateEvent返回Event*,或让generateEvent接受一个KeyEvent来避免此错误{{1}}作为参考。通过使用指针或引用,可以避免对象切片问题。

答案 1 :(得分:1)

这一行应该做什么:

KeyEvent e = generateEvent();

调用KeyEvent的构造函数,该构造函数接受Event对象或对一个对象的引用。但是,您的KeyEvent类没有这样的构造函数,因此编译器告诉您它无法从KeyEvent对象中生成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

答案 2 :(得分:1)

自动从Event转换为KeyEvent是不可能的,编译器不知道要放入什么内容,例如KeyEvent::key然后。

建议的cast解决方案要小心,因为没有类型检查,一旦收到不同类型的事件(Event一个KeyEvent,您就会遇到问题还是MouseEvent?)。常见的解决方案是添加类型ID或使用虚函数(更安全,但有时不那么简单)。

答案 3 :(得分:1)

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

最有可能的是,如果事件实际上是main,您在KeyEvent内要执行的操作是执行某些操作。这是一种常见的情况,您尝试做的事情没有任何问题。你只需要采用不同的方式。

在这种情况下,我们要做的是对事件执行“执行操作X”,其中“操作X”根据事件是KeyEvent还是其他内容而有所不同。这样做的方法是使用虚函数,如下所示:

class Event
{
public:
    string type;
    string source;

    virtual void PerformActionX();
};

然后:

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

    return 0;
}

PerformActionX的实现对于每个派生类都是不同的。该方法也可以是纯虚拟的或不是虚拟的。所有这些都取决于你想要做什么。

作为最后一点,有一些场景(以及这个问题的一些答案)建议尝试“发现”究竟是什么类型的事件e,然后转换为该类型并执行一些明确的操作(例如,如果它是特定类型,则访问key的{​​{1}}成员。这种处理称为类型开关,这通常是一个坏主意。虽然可能存在需要调用类型开关的有效方案,但处理这些情况的方式要好得多,因为它们要以面向对象的语言(使用虚函数)进行处理。首先要学习如何通过规则来做事情,并为以后留下违反规则。

答案 4 :(得分:1)

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

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

然后,您尝试将该Event对象副本再次放入KeyEvent

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

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;隐含在入口点函数中。