0

I am writing a small pub/sub application in c++14/17 for practice and self-learning. I say 14/17 because the only feature of c++17 I have used for this project so far has been the std::scoped_lock.

In this toy application, the publishers and subscribers are on the same OS process (same compiled binary).

A reasonable thing is to do in this case, is have a single class that stores the messages in an std::unordered_map<std::string, std:enque>. I plan on instantiating this class in main and passing it to the constructors of the publishers and subscribers.

The problem comes when I attempt to hold the messages into a custom queue class, with a template for different messages; for example using protobuf.

Please consider the following:

// T here represents different
// protobuf classes
template <class T>
class Queue {
public:
    std::mutex mutex_;

    void enqueueMessage(const T* message);
    const T* dequeueMessage();
    const int length() { return messages_.size();};
private:
    std::string id_;
    std::deque<const T*> messages_;
};

class Node
{
public:
    Node();
    template <class T>
    Publisher<T>* createPublisher(std::string const topic_name, Broker broker);
};

class Broker {
    public:
        template <class T>
        Publisher<T>* createPublisher(std::string const topic_name);
    private:
        /** THE PROBLEM IS HERE **/
        std::unordered_map<std::string, Queue<T*>*> queues_;
};

int main(int argc, char** argv)
{
    // this object holds the global state
    // and will be passed in when needed
    auto broker = std::make_shared<Broker>();

    EmployeeMessage empMessage = EmployeeMessage(/* params */);
    WeatherMessage  weatherMessage = WeatherMessage(/* params */);
    
    auto nodeEmp = std::make_shared<Node>();
    auto nodeWeather = std::make_shared<Node>();

    nodeEmp.createPublisher<EmployeeMessage>("name1", broker);
    nodeWeather.createPublisher<EmployeeMessage>("name2", broker);
    

}

The queues_ member of the Broker class cannot have a Type T because the Broker is not a template class.

I cannot make Broker into a template class because then I would have an instance of broker for each type of message.

How can I fix this design?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Sam Hammamy
  • 10,819
  • 10
  • 56
  • 94

1 Answers1

2

Use a base class QueueBase and stores the base class pointers.

struct QueueBase {
    virtual ~QueueBase() {}
};

template <class T>
class Queue: QueueBase {};

class Broker {
    private:
        std::unordered_map<std::string, QueueBase*> queues_;
};

When you need access queues_, dynamic_cast to the type you want. e.g.

class Broker {
        template <typename T>
        Queue<T>* get(const std::string& topic_name) {
            return dynamic_cast<Queue<T*>>(queues_.at(topic_name));
        }
};

This's just a case, pay attention to expcetion safety and error handling.

Nimrod
  • 2,908
  • 9
  • 20
  • Thanks for the answer! With this approach, is the polymorphism dynamic at runtime or at compile time? – Sam Hammamy Jul 26 '22 at 09:53
  • Mostly static I believe. Only virtual destructor is dynamic(run-time) though. – Nimrod Jul 26 '22 at 10:26
  • I kept looking around, and while some suggested `std::variant` or `std::any` I believe this to be the approach that makes sense to me and my design decisions. Thanks again @Nimrod for your answer – Sam Hammamy Jul 26 '22 at 21:07