how to push data into different queues based on the data type in cpp?

Viewed 122

I have problems to push data into different queues based on the data type.

To be specific, I have multiple request classes. For example:

class RequestA {
};
class RequestB {
};
class RequestC {
};
...

These requests might be inherited by certain class (e.g., class Request).

Each object of these data types must be put into different queues. For example:

std::vector<RequestA> queueA;
std::vector<RequestB> queueB;
std::vector<RequestC> queueC;
...

The reason why I need different queues for each class is that each request at the top (front) of the queue requires different (post-)processing steps later.

For instance,

class RequestHandler {
public:
  void tick() {
     // Multiple requests might be accumulated in the queue before calling tick()
     if (!queueA.empty()) {
        processA(queueA.front())
        queueA.pop_front(); // assume std::vector has pop_front()
     }
     // queueB can be empty although other queues are full of elements
     if (!queueB.empty()) {
       ...
     }
     ...
     // Polymorphism might be used.
     // if RequestA, RequestB, ... are derived by Request:
     if (!poly_queue.empty()) {
        Request* req = poly_queue.top();
        req->process(/* status of handler */);
        poly_queue.pop_front(); // assume std::vector has pop_front() 
     // However, the result is not same as one using separated queue,
     // because the order of requests is not preserved.
     // For example, in the separated queue example, the processing order might be, 
     // RequestA -> Request C or only RequestA. 
     // However, if I use the single queue, we cannot achieve same result.
     
   }
private:
 std::vector<RequestA> queueA;
 std::vector<RequestB> queueB;
 std::vector<RequestC> queueC;
...
 std::vector<Request*> poly_queue;
};

However, the problem is that ,to push data into the queue, I might use followings:

if (typeid()) {
   queueA.push_back();
} else if (typeid()) {
   queueB.push_back();
} ...

or if RequestA, RequestB, ... are inherited by class Request:

std::map<std::type_index, std::vector<Request*>> queue;

queue[typeid()].push_back(...);

might be used.

However, it might be better to avoid using typeid or dynamic_cast.

How to efficiently handle this scenario?

1 Answers

One possible way is to use templates and std::map. In particular, you can create a member function template named addRequest for the RequestHandler class to handle different requests.

Method 1

Step 1

Create a std::map.

std::map<std::type_index, void*> myMap{{typeid(RequestA), &queueA}, 
                                       {typeid(RequestB), &queueB}, 
                                       {typeid(RequestC), &queueC}};

Step 2

Add declaration for member function template add<> inside class RequestHandler.

template<typename T> void addRequest(T Arg);

Step 3

Implement addRequest member function template.

template<typename T> void RequestHandler::addRequest(T Arg) // - STEP 3
{
    
   
    (*static_cast<std::vector<decltype(Arg)>*>(myMap.at(typeid(Arg)))).push_back(Arg);
    
}

Working example

#include <iostream>
#include <map>
#include <vector>
#include <typeindex>


struct RequestA{};
struct RequestB{};
struct RequestC{};


class RequestHandler {
    public:
        void tick() 
        {
            std::cout<<"tick called"<<std::endl;
        }
    private:
        std::vector<RequestA> queueA;
        std::vector<RequestB> queueB;
        std::vector<RequestC> queueC;
 
        //create std::map -                             STEP 1
        std::map<std::type_index, void*> myMap{{typeid(RequestA), &queueA}, 
                                               {typeid(RequestB), &queueB}, 
                                               {typeid(RequestC), &queueC}};
    public:
        //create member function template -             STEP 2
        template<typename T> void addRequest(T Arg);

};
template<typename T> void RequestHandler::addRequest(T Arg) // - STEP 3
{
    std::cout  << "add called on " <<typeid(Arg).name() << std::endl;//just for debugging purposes
    std::cout << "size before push_back "<< (*static_cast<std::vector<decltype(Arg)>*>(myMap.at(typeid(Arg)))).size()<<std::endl;//just for debugging
    (*static_cast<std::vector<decltype(Arg)>*>(myMap.at(typeid(Arg)))).push_back(Arg);
    std::cout << "size after push_back "<< (*static_cast<std::vector<decltype(Arg)>*>(myMap.at(typeid(Arg)))).size()<<std::endl;

    std::cout<<"--------------------------------------"<<std::endl;
}


int main()
{
    
    RequestA A;
    RequestB B;
    RequestC C;
    
    RequestHandler rq;
    
    //call RequestHandler's add method simulating the requests
    rq.addRequest(A);
    
    rq.addRequest(B);
    rq.addRequest(B);
    
    rq.addRequest(C);
    
}

Working demo


Method 2

With C++17 we can use std::any instead of void*. The basic steps remains the same as in the previous method.

#include <iostream>
#include <map>
#include <vector>
#include <typeindex>
#include <any>
#include <functional>
struct RequestA{};
struct RequestB{};
struct RequestC{};


class RequestHandler {
    public:
        void tick() 
        {
            std::cout<<"tick called"<<std::endl;
        }
    private:
        std::vector<RequestA> queueA;
        std::vector<RequestB> queueB;
        std::vector<RequestC> queueC;
 
        //create std::map -                             STEP 1
        std::map<std::type_index, std::any> myMap{{typeid(RequestA), std::ref(queueA)}, 
                                                  {typeid(RequestB), std::ref(queueB)}, 
                                                  {typeid(RequestC), std::ref(queueC)}};
    public:
        //create member function template -             STEP 2
        template<typename T> void addRequest(T Arg);

};
template<typename T> void RequestHandler::addRequest(T Arg) // - STEP 3
{
    std::cout  << "add called on " <<typeid(Arg).name() << std::endl;//just for debugging purposes
    std::cout << "size before push_back "<< std::any_cast<std::reference_wrapper<std::vector<T>>>(myMap.at(typeid(T))).get().size()<<std::endl;//just for debugging
   
    std::any_cast<std::reference_wrapper<std::vector<T>>>(myMap.at(typeid(T))).get().push_back(Arg);
    std::cout << "size after push_back "<< std::any_cast<std::reference_wrapper<std::vector<T>>>(myMap.at(typeid(T))).get().size()<<std::endl;

    std::cout<<"--------------------------------------"<<std::endl;
}


int main()
{
    
    RequestA A;
    RequestB B;
    RequestC C;
    
    RequestHandler rq;
    
    //call RequestHandler's add method simulating the requests
    rq.addRequest(A);
    
    rq.addRequest(B);
    rq.addRequest(B);
    
    rq.addRequest(C);
    
}

Working demo

The above is just a demonstration according to my current understanding of your requirements. It took me a while(around 40 minutes) to understand the requirements and writing the code accordingly. I may still be wrong in understanding your requirements. So let me know if this is what you wanted. You can also modify the code according to your needs.

Related