mercredi 21 avril 2021

Generic Messaging when concrete Messages are autogenerated C++ classes from XML

Background

I have auto generated concrete message types from a XML -> C++ generator.

GenMsg1, GenMsg2, ... , GenMsgN

All of these generated classes are from an XML schema. Technically I can edit their cpp and hpp files but I would prefer to not touch these as much as possible. They all have guaranteed functions that I would like to be able to call generically.

NOTE: I cannot get away from the above situation as this is a design limitation from another project. Also, I just used raw pointers in this simple example. I understand this is not best practice, its just for showing a general idea.

Goal

I am looking to process the above generated messages generically on my side.

Idea 1 and 2

My first idea was to just create and general "Message" class that was templated to hold one of the above types with a simple enum for identifying what type of message it is. The problem with this is I cannot just pass around a pointer to Message because it needs the template type parameter so this is obviously a no-go.

My next thought was to use the Curiously Recurring Template Pattern but that has the same issues as above.

Idea 3

After a lot of reading on messaging frameworks my next thought was that std::variant might be an option. I have the following example which works but it uses double pointers and templated functions to access. If the wrong datatype is used this will throw an exception at runtime (which makes it quite clear this is the issue) but I could see this being annoying down the line as far as tracking the source of the throw.

I keep trying to read up on the std::visit but it does not make a whole lot of sense to me. I do not really want to implement a separate visitor class with a bunch of functions by hand when all of the functions in the generated classes are autogenerated already(like foo in the example below) and are ready to be called when the type is known. Additionally, they are guaranteed to exist. So it would be kind of nice to be able to call a foo() in Message and have it dive into the internal Representation and call its foo.

I have a MsgType enum in there that I could use as well. When the internal representation is set, I could set that and use it for deducing type... But this seems like its just duplicating effort already done by the std::variant so I scrapped its use but kept it in the code blow in case someone here had a new idea where something like that could be useful.

Any ideas on design moving forward? This seems like the most promising route, but I am open to ideas.

Idea 3 Code

#include <iostream>
#include <variant>

enum class MsgType { NOTYPE = 0, GenMessage1 = 1, GenMessage2 = 2, GenMessage3 = 3 };

class GenMessage1
{
public:
    void foo() {std::cout << "Msg 1" << std::endl;}
};

class GenMessage2
{
public:
    void foo() { std::cout << "Msg 2" << std::endl; }
};

class GenMessage3
{
public:
    void foo() { std::cout << "Msg 3" << std::endl; }
};

class Message
{
private:
    MsgType msgType;
    std::string xmlStrRep;
    std::variant<GenMessage1*, GenMessage2*, GenMessage3*> internalRep;
public:

    Message()
    {
        this->msgType = MsgType::NOTYPE;
        this->xmlStrRep = "";
    }

    template <typename T>
    void setInternalRep(T* internalRep)
    {
        this->internalRep = internalRep;
    }

    template <typename T>
    void getInternalRep(T retrieved)
    {
        *retrieved = getInternalRepHelper(*retrieved);
    }

    template <typename T>
    T getInternalRepHelper(T retrieved)
    {
        return std::get<T>(this->internalRep);
    }

    void foo()
    {
        //call into interal representation and call its foo
    }
};

int main()
{
    Message* msg = new Message();
    GenMessage3* incomingMsg = new GenMessage3();
    GenMessage3* retrievedMsg;
    msg->setInternalRep(incomingMsg);
    msg->getInternalRep(&retrievedMsg);

    retrievedMsg->foo();

    return 0;
}

Outputs:

Msg 3

Aucun commentaire:

Enregistrer un commentaire