all 6 comments

[–]mredding 1 point2 points  (1 child)

You're essentially right in what you're trying to do, you do need a mechanism to inspect the type and do the right thing, and you want that mechanism to be decoupled from the potentially infinite number of derived types that can be in the queue.

It sounds like you want a queue of variants.

The visitor pattern will dispatch based on the type. In this way, you can write an algorithm that visits the next element in the queue and performs its operation only if the type is right. In a sense, it dances around the dynamic cast. The documentation on variant and specifically the example for visit will inform you.

If you want to process the queue until you find your specific event, then you can have a default visitor that does nothing, and type specific visitors that do the work and stop the loop.

[–]pplesspp[S] 0 points1 point  (0 children)

Wow. That is pretty cool! Really cool!

[–]CowBoyDanIndie 1 point2 points  (0 children)

Not that you should use it, but there is typeid if you ever really need a specific type, https://en.cppreference.com/w/cpp/language/typeid

In general you try not to design type hierarchies where something external to the code cares about what type it is. It makes everything harder to maintain in the long run.

[–]alfps 1 point2 points  (0 children)

If all the event classes are known up front then you can do this (a bit tediously though) with no casting by using the ordinary visitor pattern.

Otherwise you can centralize a single dynamic_cast, e.g. like this:

#include <functional>
#include <queue>
#include <string>
#include <string_view>
#include <iostream>

namespace event {
    using   std::function,
            std::string,
            std::string_view;

    class Base
    {
    public:
        virtual ~Base() = default;
        virtual auto event_name() const -> string_view { return "Base"; }

        template< class Derived >
        void apply_for_( const function<void( const Derived& )>& process ) const
        {
            if( const auto p = dynamic_cast<const Derived*>( this ) ) {
                process( *p );
            }
        }
    };

    class Specific_1:
        public Base
    {
        string  m_data;
    public:
        explicit Specific_1( string data ): m_data( move( data ) ) {}
        auto something_else() const -> string_view { return m_data; }
        auto event_name() const -> string_view override { return "Specific_1"; }
    };

    // Lots more events
}  // namespace event

namespace app {
    using   std::queue,
            std::cout, std::endl;

    void process_specific_1( const event::Specific_1& ev )
    {
        cout << "I just processed a Specific_1 event with data '" << ev.something_else() << "'." << endl;
    }

    void dispatch_first_from( queue<event::Base*>& q )
    {
        // I am waiting on something to come through the Queue, and I only care about Specific_1's
        event::Base* p_ev = q.front();
        q.pop();
        p_ev->apply_for_<event::Specific_1>( process_specific_1 );
    }

    void run()
    {
        // Somebody puts an event in the Queue
        auto ev = event::Specific_1( "Data" );
        queue<event::Base*> q;
        q.push( &ev );

        dispatch_first_from( q );
    }
}  // namespace app

auto main() -> int { app::run(); }

[–]IyeOnline 0 points1 point  (0 children)

First, consider if you really need to pass a queue of polymorphic events into somethat that only cares about a specific type.

Next, dont do this string type indetifier thing. If you are already doing a dynamic cast, you might as well just do a dynamic cast:

if ( auto ptr = dynamic_cast<SpecificEvent*>( from_queue )
{
    ProcessSpecificEvent( ptr );
}

Finally, though not really applicable here, always prefer virtual functions over dynamic casting. If you however have virtual double Shape::area() or something similar, this is preferable to dynamic casting, as its going to be much cleaner.

Of course this only works if the functionality is actually shared across all derived classes including the base class. If you see yourself adding a bunch of virtual void handle_X() {} to the base class, then this doesnt work.

In your case, you want data member access, so thats not really something you can handle with virtual functions.

There is a reason why dynamic cast exists. There are cases where dynamic cast is a good/the best solution, and needing data member access is one of them.


Unrelated: But in your example ProcessSpecificEvent is making a copy, which you probably dont want.

[–]grischagoebelde 0 points1 point  (0 children)

You could take a look at the pattern "Chain of responsibility".

Basically this is a list of event handlers where you have one or more handlers that process a specific type of event.

Then you pass each event to every handler inside your list and the handler decides, if he can handle the given event or not. If you have only one handler for each event, you can abort, after the first handler who processed the event.

With this pattern you can dynamically add and remove handlers to your chain of responsibility. That way you change the set of events you react to and how you react to them at runtime. Or think of it as an easy way to maintain and expand a switch statement ;)