Reputation: 337
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
Upvotes: 1
Views: 1334
Reputation: 385144
Your function generateEvent
does the following:
KeyEvent
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.
Upvotes: 2
Reputation: 437376
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.
Upvotes: 1
Reputation: 39208
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;
}
Upvotes: 1
Reputation: 3056
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).
Upvotes: 1
Reputation: 4711
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.
Upvotes: 1