3

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?

sungjun cho
  • 809
  • 7
  • 18
  • Perhaps it could be solved through inheritance and polymorphism? Using things like `typeid` tend to be a sign of bad design. – Some programmer dude Apr 08 '22 at 17:07
  • 1
    Can you explain why "*different (post-)processing steps later*" can't be handled with polymorphism? – MatG Apr 08 '22 at 17:07
  • @MatG That is because I have to process them (at the top of each queue) only one at a time (once the certain function is called), which means that they are processed in the order of insertion. – sungjun cho Apr 08 '22 at 17:09
  • Unless you're using threads to "process" the data in the queues, you will not be doing in parallel. So, unless you want to keep the top element in the queue once it's processed, why not simply call the (virtual) processing function of the top item, pop it, and then do the next? – Some programmer dude Apr 08 '22 at 17:13
  • If you really need to do something different for derived classes, use the visitor pattern. – Anon Mail Apr 08 '22 at 17:19
  • @Someprogrammerdude I added the example to process request in each queue. I think it might be hard to use polymorphism in my scenario. – sungjun cho Apr 08 '22 at 17:19
  • It may be beneficial to clarify why `processA` can't be a `virtual Request::process()` polymorphic method, maybe you need different signatures? – MatG Apr 08 '22 at 17:26
  • @MatG I added some details in the example. It is possible to use polymorphism. However, it is only possible when I use single queue. In this case, the order of requests pushed into the queue is different compared to one using separated queue. – sungjun cho Apr 08 '22 at 17:32
  • Sounds like you just need a priority queue then. – Taekahn Apr 08 '22 at 17:43
  • While it could still be a problem with the design, the analysis or the requirements (which is the direction I lean to), it could also be a problem that the implementation of the design is flawed. When, where and how are the different request objects created? Why can't they be pushed to respective queue when they are created and you actually know the concrete type? – Some programmer dude Apr 08 '22 at 17:54
  • @Someprogrammerdude To be specific, there exist multiple request generators that send requests to the request handler. The request generator generates the request when certain event occurs. Each request generator generates `concrete` request at this time and push them to the request handler (push requests to the respective queue in the request handler). The reason why request generator does not process request at the time of request generation is that the request handler processes them later when the certain event occurs in the request handler. The – sungjun cho Apr 08 '22 at 18:17
  • @Someprogrammerdude request handler processes one request for each type only if the request exists at the top of the repective.queue. – sungjun cho Apr 08 '22 at 18:30

1 Answers1

1

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.

Jason
  • 36,170
  • 5
  • 26
  • 60