Casting base-to-derived loses information

In a game I'm working on, I have an event system that looks like this:

event.hpp
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
namespace a_game {
    namespace engine {
        namespace input {
            enum class event {
                closed,
                keydown,
                keyup,
                keypressed
            };

            struct event_args {};

            struct closed_event_args : public event_args {};

            struct keypressed_event_args : public event_args {
                keyboard::key key;
            };

            void raise(event type, const event_args& args = event_args());

            typedef int(* event_handler)(const event_args& args);

            class event_queue {
                friend void raise(event type, const event_args& args);
            public:
                // Modifiers.
                event_queue& operator+=(event_handler handler);

                event_queue& operator-=(event_handler handler);
            private:
                std::vector<event_handler> handlers;

                // Actions.
                void dispatch(event type, const event_args& args) const;
            };

            extern std::map<event, event_queue> events;
        }
    }
}


event.cpp
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
namespace a_game {
    namespace engine {
        namespace input {
            std::map<event, event_queue> events;

            void raise(event type, const event_args& args)
            {
                events[type].dispatch(type, args);
            }

            event_queue& event_queue::operator+=(event_handler handler)
            {
                for (event_handler h : handlers) {
                    if (h == handler)
                        return (*this);
                }
                handlers.push_back(handler);
                return (*this);
            }

            event_queue& event_queue::operator-=(event_handler handler)
            {
                for (std::vector<event_handler>::iterator it = handlers.begin();
                                                    it < handlers.end(); ++it) {
                    if (*it == handler) {
                        handlers.erase(it);
                        return (*this);
                    }
                 }
            }

            void event_queue::dispatch(event type, const event_args& args) const
            {
                for (event_handler handler : handlers) {
                    switch (type) {
                        case event::closed:
                            handler(args);
                            break;
                        case event::keypressed:
                            handler(args);
                            break;
                        default:
                            break;
                    }
                }
            }
        }
    }
}


game.cpp
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
        static int closed_event_handler(const closed_event_args& args)
        {
            window.close();
            return 0;
        }

        static int keypressed_event_handler(const keypressed_event_args& args)
        {
            std::cout << "Key " << int(args.key) << " pressed" << std::endl;
            switch (args.key) {
                case keyboard::key::escape:
                    raise(event::closed);
                    break;
                default:
                    break;
            }
            return 0;
        }

        static void init_events()
        {
            events[event::closed] += (event_handler)closed_event_handler;
            events[event::keypressed] += (event_handler)keypressed_event_handler;
        }


The problem is that when the handler for the "keypressed" event is called, the keypressed_event_args is casted to event_args and then back again, which results in loss of information - specifically, I should get a value of 58 if ENTER is pressed, but instead I get a random integer value (e.g. -1574636800) which suggests it's uninitialised. I'm aware that this is because the compiler gets rid of the extra variables that were being used by the derived instance.

How can I rework this event system so that the contents of the event_args derived classes is preserved?
You could use a union and a tag to determine what arguments actually exist in the structure...AFAIK this is basically what SFML does for their events.
That's what I would normally do, but I thought it would be more elegant this way. Never mind, I'll do it the union way unless someone has another suggestion.
Last edited on
the keypressed_event_args is casted to event_args and then back again, which results in loss of information
No, casting does not lead to loss of information.

where the loss could happen is when you copy only parts of the object and you use the copy.

copying parts may happen when you copy a derived class to a base class which is legal, but only that part relevant for the base class is copied. if you cast that base class to the derived class (not dynamic_cast) you will see the loss of information
dynamic_cast is only defined for casting from base to derived. I know ordinary casting doesn't lead to loss of information, I was saying casting from derived to base was doing so, because the compiler only copies the relevant data and event_args is an empty struct. keypressed_event_args::key was therefore not being copied, so when I casted back to keypressed_event_args from event_args, 'key' was not being initialised. I was well aware of what was causing my problem, I was just asking for a way to fix it without changing the overall system I was using because I liked it. In the end I got it working with firedraco's suggestion.
Last edited on
Looks to me like the problem is due to casting the callback:

 
events[event::keypressed] += (event_handler)keypressed_event_handler;


The fact that you had to cast here is a sign that you're doing something wrong. You shouldn't just cast around compiler errors.

You have two functions here that have 2 different signatures.

Your callback takes a int foo(const event_args& args);
Vs what you're giving it: int foo(const keypressed_event_args& args)

And while keypressed_event_args is derived from event_args, this does not mean that you can treat them as the same type or as the same function signature.

You need to cast the pointer/reference.... not the callback.




That said, this can still be accomplished without the use of a union by doing some template trickery to handle the cast + adding the functionality to pass some userdata to the callback (ie: make it a std::function rather than using a raw function pointer).

The idea is to have a template callback within your queue class which does the parameter cast. Something like:

1
2
3
4
5
template<typename T>  // where 'T' is a type derived from event_args
int callback( int (*userfunc)( const T& args ), const event_args& args )
{
    return userfunc( dynamic_cast<const T&>( args ) );  // or static_cast if you trust it
}


You'd then add this to your callbacks... which of course would change your event_handler signature. If you do this with std::function, you can bind this so the first parameter (and thereby the template) get omitted from the signature

 
typedef std::function<int(const event_args&)> event_handler;


(it might also be wise to change the above 'callback' function to also use a std::function instead of a raw pointer, but whatever).

Then you can do this:

1
2
3
4
5
6
template <typename T>
event_queue& operator+=(int (*handler)( const T& args ) )
{
    queue.push_back(  std::bind( &Queue::callback, handler, std::placeholders::_1 ) );
    return *this;
}


But it would make removal a little trickier. And has a downcast. And is additional overhead in the callbacks.

Personally I don't think this approach is any better than using a union. But if you really don't want to use a union it's certainly an alternative.


EDIT: actually I'm not even sure this would work either... since this also requires the callback to be cast. So yeah this wouldn't work after all.


Just use a union.
Last edited on
@Disch
Thanks for the in-depth answer. In the end, after a lot of refactoring to accommodate for game state management, I had to use a union. In fact, I had to abandon the callback structure altogether in favour of polling. Now all I have is a thin wrapper around SFML's event structure. I'd rather use callbacks because there's less duplicated code that way (although it will be refactored out eventually) but it's too inefficient - every time the game changes state, the event queues would have to be swapped, and I'd also have to store multiple event queues in memory. There are ways around doing it that way but in the end it was easier just to use polling. Maybe when I go on another refactoring spree I can go back to using callbacks.
Last edited on
Why not just have the game state be your event handler and use inheritance as the logic manager? That way there's no duplicated code and no need for callbacks (virtual methods replace the need for them)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class GameState
{
protected:
    // to be implemented by derived classes
    virtual void logic() = 0;
    virtual void draw() = 0;

    // optionally implemented
    virtual void keyPress(int key) { }
        // ... & whatever other events you want

public:
    void run()
    {
        while(running)
        {
            pollEventsAndDispatchToVirtualMethods();

            logic();

            draw();
            window.display();

            regulateFramerate();
        }
    }
};
Topic archived. No new replies allowed.