1

First off: I searched half the web to find an answer with this as a solution that came closest. It is, however, too heavyweight for me though so I am looking a little less complex.

Well then, some context: I am building a system which should be able to process incoming messages from a queue and then store the outcome of these messages in another queue. I would like to store these responses in a generic class because I am storing it in a multimap.

The response class currently is defined as follows

class COutboundMsg  
{  
public:  
    enum eTypeHint {  
        thNone,  
        thSomeType,  
        thLast  
    };  

    eTypeHint m_TypeHint;

    void* m_OutboundData;

    COutboundMsg(COutboundMsg::eTypeHint TypeHint, void* data);
};

COutboundMsg::COutboundMsg(COutboundMsg::eTypeHint TypeHint, void* data) :
m_TypeHint(TypeHint),
m_OutboundData(data)
{
}

Now, the current way of working would involve a user to do something like this:

CSomeType* data = new CSomeType();  
COutboundMsg(COutboundMsg::thSomeType , (void*) data);

It would be up to the user at the other end to cast the void* back to CSomeType* (using the type hint) and delete it.

It don't like it.

I'd rather have the m_OutboundData contained in an auto_ptr or something and make sure that it deletes itself when done.

Any ideas? Maybe a different approach altogether

Nebula
  • 1,045
  • 2
  • 11
  • 24
  • 2
    The idea of having an enum to manage a type, and to cast things back and fort from void* is really C-style oriented :) You should probably thing of a class hierarchy with your COutboundMsg as a base and then your derived classes to manage thNone, thSomeType and thLast – sergico Apr 01 '11 at 10:58
  • Yeah I know ;-) I have thought of using the response as a base class - I already do so in the request queue - but to me it sounds a bit bloated to do it for the response as well. I'd have to create an additional class for every class I'd like to send back as response. I was hoping on someone to hint toward a different approach? If there is none I can always fall back to using the `COutboundMsg` as a base class. – Nebula Apr 01 '11 at 11:05
  • `void*` in C++.... .... .... `` – Nim Apr 01 '11 at 11:20
  • That's why I'm asking here ;-) Teach me better! – Nebula Apr 01 '11 at 11:23

3 Answers3

3

Normally polymorphism is used for a system like this; any item that can be put in the Outbound queue derives from QueueItem and the queue contains QueueItem* or a smart pointer to a QueueItem.

Since the items all derive from a common base, you can safely delete the QueueItem* or allow a smart pointer to handle it. This approach also allows you to use dynamic_cast to make sure that the pointer is, in fact, pointing to an object of the type you think it is.

If this approach is possible, be sure to make the QueueItem destructor virtual, otherwise the derived class's destructor won't be called.

Collin Dauphinee
  • 13,664
  • 1
  • 40
  • 71
  • This answer leads me to think that this actually a fairly simple problem I'm stating ;-) This is actually exactly how I implemented the incoming message queue: a queue of base class shared pointers. Still the overhead of additional classes just to wrap another class into my response seems like something that can be done more efficiently? Excuse me for being so pigheaded, but I'd like to be absolutely sure I'm choosing the best solution. I really like to improve my C++ skills and I'm hoping to learn from the members here! – Nebula Apr 01 '11 at 11:19
  • Also, how would I get the originating type back for the `dynamic_cast`? Would it be ugly to use the `enum` for this? – Nebula Apr 01 '11 at 11:34
  • Yes, you don't have much choice other than passing some identifier (your enum) and a pointer to the data. This is how, for example, Windows' messaging system works. C++ is statically typed, so you can't store different types of objects in the same queue without also storing some data to identify what the object is, and then casting it. – Collin Dauphinee Apr 01 '11 at 11:47
  • Right I see, well I guess it makes sense. Since the other queue is implemented as this as well even more so. I'll take this approach. Thanks to you all! – Nebula Apr 01 '11 at 12:22
  • @dauphic: for polymorphic messages, you can inherit from a base-message and upon receive always call the virtual function processMsg(), which can dispatch to the proper destination. So no enum or dynamic_cast is needed. – stefaanv Apr 01 '11 at 12:37
  • @stefaanv: That is true if we were to process the message. That's actually how my incoming message queue works. The topic of discussion is return data however. This means that one response might have a m_SessionId member whereas the other has a m_Measurement member. Both of completely different types and no correlation. Therefore I do need to know the type and a dynamic cast in order to access the response dependent data. (btw sorry, my cell has no backticks ;) ) – Nebula Apr 03 '11 at 08:52
  • @Nebula: ah, okay. In our software, if needed, the sender blocks until the message is handled and we get the reply straight from the message. If you can't work synchronously, the reply is actually the same as a request message, so it can be handled polymorphically, since it also triggers an action and it should be sent to the same queue. – stefaanv Apr 03 '11 at 09:19
2

shared_ptr stores a deleter to use to delete the object, and so does not depend on the static type of the pointer at the time of deletion. So something like this should do the right thing:

COutboundMsg(eTypeHint TypeHint, shared_ptr<void> msg);

shared_ptr<CSomeType> data(new CSomeType);
COutboundMsg(COutboundMsg::thSomeType, data);
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • I though creating a shared_ptr with `void` as template argument was forbidden because `void` does not define an object? Oh wait, the boost site actually suggests this ;-) Still, I'd rather not lose my type info. – Nebula Apr 01 '11 at 11:12
  • The type info is captured by the ctor and saved at runtime (which is the only possible way if you're storing arbitrary types) – MSalters Apr 01 '11 at 14:52
  • However, the type info isn't made available, so the only improvement this offers is correct deletion. The other answers are better if you'd like safe type conversions too. – Mike Seymour Apr 01 '11 at 15:55
1

If the set of messages is known before hand (transmit/receive), rather than polymorphism, consider a variant type, I'm taking specifically about boost::variant<>.

The advantage of this approach is that each message is a specific type (I know it seems like you don't want to implement this - however as a long term design feature, I think you'll find this approach extensible) and the variant describes the set of messages, let me show a quick example:

let's say I have two messages

struct Logon{}; 
struct Logout{};

I define a variant to hold the possible set of messages, at a given time it's only ever one of them..

typedef boost::variant<Logon, Logout> message_type;

Now I can store this in any container, and pass it around like normal, the only difference is that the truly generic way to access them is via visitors, e.g.

struct process : boost::static_visitor<>
{
  void operator()(Logon const& cLogon)
  {
    // do stuff
  }
  void operator()(Logout const& cLogout)
  {
    // do stuff
  }
};

// method that processes a given message
void processMessage(message_type const& cMsg)
{
  // don't know what this message is, that's fine, hand it to the visitor
  boost::apply_visitor(process(), cMsg); // the correct overload will be called for the message
}

...I think it's slightly better than void* ;)

Nim
  • 33,299
  • 2
  • 62
  • 101
  • Thanks! I did not know about variants yet. Sounds interesting, let me look into this. I am already using boost, so it might be a viable option since it saves creating new files for new response types. Since we are using SVN this option should save time :-D I might come back with some follow up questions though! – Nebula Apr 01 '11 at 11:46
  • Ok, so the variant concept is certainly interesting. Though I also read that there is a performance penalty. This I cannot accept. I'll keep it in mind for other applications though. There, you see, still taught me something :-) – Nebula Apr 01 '11 at 12:21