سؤال

I have the following example code:

#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;
}

I can't compile it, G++ throws an error of kind:

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

I know that the error is obvious for C++ guru's, but I can't understand why I can't do the conversion from base class object to derived one. Can someone suggest me the solution of the problem that I have? Thx in advice

هل كانت مفيدة؟

المحلول

Your function generateEvent does the following:

  • creates a KeyEvent
  • converts (upcasts) the object into an Event by copying and slicing
  • returns that Event object

You then try to take that Event object copy and put it into a KeyEvent again.

You are trying to use polymorphism but are actually just slicing. Consider (with caution!) dynamic allocation:

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
}

Also note that return 0; is implicit in the entrypoint function.

نصائح أخرى

The generateEvent function passes an Event by value (rather than by pointer or reference). This means that the KeyEvent object you are trying to pass will be "sliced" down to an Event object, and the key and modifier fields will be discarded.

The error message is not the most helpful, but what the compiler is trying to say is that it can't convert an Event value to a KeyEvent value. The conversion would require synthesizing default values for the KeyEvent fields, because the original fields were sliced away when the Event object was returned by value.

You can avoid this error by either dynamically allocating a KeyEvent in generateEvent, and having generateEvent return an Event*, or by having generateEvent accept a KeyEvent by reference. By using a pointer or reference, you can avoid the object slicing problem.

It's just impossible to automatically convert from Event to KeyEvent, the compiler doesn't know what to put into e.g. KeyEvent::key then.

Be careful with the proposed cast solutions, as there is no type checking, and you will have problems as soon as you receive events of different types (is an Event a KeyEvent or a MouseEvent?). Common solutions are to add type ids or to use virtual functions (safer, but sometimes way less straightforward).

What this line is supposed to do:

KeyEvent e = generateEvent();

is call a constructor of KeyEvent that takes either an Event object or a reference to one. Your KeyEvent class, however, does not have such a constructor, so your compiler is telling you that it can't make a KeyEvent out of an Event object ("error: conversion from 'Event' to non-scalar type 'KeyEvent' requested").

What you could use instead is this code:

#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

The problem you have is based on the fact that while generateEvent does in fact create a KeyEvent, this is something that only the programmer (you) knows. What the compiler knows is that generateEvent returns an Event, which in the general case is not in fact a KeyEvent. Therefore, the compiler complains that you are treating something that formally (as the function definition states) is not a KeyEvent as a KeyEvent.

Most probably, what you want to do inside main is to perform some action if the event is in fact a KeyEvent. This is a common scenario, and there is nothing wrong with what you are trying to do. You just need to do it differently.

In this case, what we want to do is "perform action X" on the event, where "action X" is something different depending on whether the event is a KeyEvent or something else. The way to do it is with virtual functions, like so:

class Event
{
public:
    string type;
    string source;

    virtual void PerformActionX();
};

And then:

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

    return 0;
}

The implementation of PerformActionX would be different for each derived class. The method could also be pure virtual or not. All this is dependent on what exactly you would want to do.

As a final note, there are scenarios (and some answers to this question) which suggest trying to "discover" exactly what type of event e is, and then casting to that type and performing some explicit action (like e.g. accessing the key member of a KeyEvent) if it is a specific type. This kind of handling is called a type switch, and it's generally a bad idea. While there might be valid scenarios where a type switch is called for, it is a much better idea to handle such cases the way they are meant to be handled in an object-oriented language (with virtual functions). First learn how to do things by the rules, and leave breaking the rules for later.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top