我有以下示例代码:
#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
答案 0 :(得分:1)
generateEvent
函数按值传递Event
(而不是通过指针或引用)。这意味着您尝试传递的KeyEvent
对象将被“切片”为Event
个对象,key
和modifier
字段将被丢弃。
错误消息不是最有用的,但编译器试图说的是它无法将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;
}
答案 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;
隐含在入口点函数中。