【问题标题】:Base class -> Derived class and vice-versa conversions in C++基类 -> 派生类,反之亦然 C++ 中的转换
【发布时间】:2024-01-17 12:34:01
【问题描述】:

我有以下示例代码:

#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++ 大师的错误很明显,但我不明白为什么我不能将基类对象转换为派生对象。有人可以建议我解决我遇到的问题吗?谢谢建议

【问题讨论】:

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


    【解决方案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; 隐含在入口点函数中。

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

        【解决方案3】:

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

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

        【讨论】:

          【解决方案4】:

          这条线应该做什么:

          KeyEvent e = generateEvent();
          

          调用KeyEvent 的构造函数,该构造函数接受Event 对象或对其的引用。但是,您的 KeyEvent 类没有这样的构造函数,因此您的编译器告诉您它不能从 Event 对象中生成 KeyEvent(“错误:从“事件”转换为非标量类型 '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

          【讨论】:

          • 谢谢,很好的解决方案 :) 这是 RTTI 的东西,对吧?我必须了解更多关于 C++ 中的内存/指针和类型管理...
          • @Ivan:是的,dynamic_cast 使用 RTTI。
          【解决方案5】:

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

          最有可能的是,如果事件实际上是 KeyEvent,您想要在 main 中执行一些操作。这是一种常见的情况,您尝试做的事情并没有错。你只需要做不同的事情。

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

          class Event
          {
          public:
              string type;
              string source;
          
              virtual void PerformActionX();
          };
          

          然后:

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

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

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

          【讨论】:

            最近更新 更多