سؤال

I'm creating a mechanism by which Receivers can tell a Sender that each Receiver is interested in Messages of a certain type. With my sample implementation below there exists a limitation where a Receiver that wants to receive all Messages of a certain base type only receives Messages that are explicitly of that type and will not receive Messages of a derived type (see main() for example).

A potential solution would be to register all of a Message's ancestors' types when registering that particular Message and use that information to route Messages properly.

What other solutions are there?

Note: In reality, I'd store the RTTI so a RTTI lookup wouldn't be required every time. There are also other things that I have skimped/skipped here, as well. I'm going for brevity w/ this example...

Example code below:

class Sender
{
  typdef std::vector<Receiver const & > Receivers;
public:
  void register(Receiver const & i_recv, typeinfo const & i_type)
  {
    m_routingMap[i_type].push_back(i_recv);
  }


  void send(BaseMsg const & i_msg)
  {
    Receivers receivers = m_routingMap.find(typeid(i_msg));
    for (Receivers::iterator receiver = receivers.begin(); receiver != receivers.end(); ++receiver) {
      receiver.receive(i_msg);
    }
  }

private:
  std::map<typeinfo const &, Receivers> m_routingMap;
};


class Receiver
{
public:
  void receiver(BaseMsg const & i_msg)
  {
    // React to expected messages here
  }
};


class BaseMsg {};

class ChildMsg : public BaseMsg {};

int main()
{
  Sender sndr;

  Receiver recv1;
  sndr.register(recv1, typeid(BaseMsg));

  Receiver recv2;
  sndr.register(recv2, typeid(ChildMsg));

  BaseMsg baseMsg;
  sndr.send(baseMsg); // I want only recv1 to receive this message

  ChildMsg childMsg;
  sndr.send(childMsg); // I want both Receivers to receive this message, but only recv2 will receive it
}

Update: here's a solution I'm getting up to:

// Note: implementation is based in gleaning from
// http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14

class BaseMsg
{
public:

  typedef std::vector<TypeInfo const & > Types;

  static TypeInfo const * getType()
  {
    TypeInfo static * ms_type = new TypeInfo(typeid(BaseMsg));
    return ms_type;
  }

  static Types const * getAncestorTypes()
  {
    // The base class does not have an ancestor
    // Static varible, will only be constructed once!
    Types * ms_ancestorTypes = new Types();
    return ms_ancestorTypes;
  }
};


class ChildMsg
{
public:
  static TypeInfo const * getType()
  {
    TypeInfo static * ms_type = new TypeInfo(typeid(ChildMsg));
    return ms_type;
  }

  static Types const * getAncestorTypes()
  {
    // Add the parent type and all the parent's ancestor's types
    Types const * ancestorTypes = BaseMsg::getAncestorTypes();

    // Static variable, so it will only be constructed once!
    Types * static ms_ancestorTypes = new Types(ancestorTypes->begin(), ancestorTypes->end());

    // This push_back() will occur every time, but it's only one operation,
    // so hopefully it's not a big deal!
    ms_ancestorTypes->push_back(BaseMsg::getType());

    return ms_ancestorTypes;
  }
};

And the Sender:

# Python pseudo code
class Sender:
  def send(self, i_msg):
    types_to_check_for = [i_msg.getType()].extend(i_msg.getAncestorTypes())

    for type_ in types_to_check_for:
      for receiver in _routing_list[type_]:
        receiver.receive(i_msg)
هل كانت مفيدة؟

المحلول

Perhaps consider using an observer pattern (http://en.wikipedia.org/wiki/Observer_pattern).

This way you sender has no knowledge of your receiver, and your observer can control the distribution of msgs.

Sender -> informs observer there is a message.

observer -> informs each interested party there is a new msg.

interested part -> does fun stuff.

This will require some sort of msg identification system. Perhaps all msgs could inherit from a msg type that has a type member and an id member. That way you can register for msgs using them.

Update:

A quick msg structure:

class Message
{
public:
    size_t m_Type;
    size_t m_Id;

protected:
    Message(size_t type, size_t id) : m_Type(type), m_Id(id) {}
};

class Type1 : public Message
{
public:
    static const size_t type = 1;
    Type1(size_t id) : Message(type, id) {}
};

The subscriber means the person that wants to listen to the msg). The subscriber should have an interface to accept msgs based on both of these functions.

Class subscriber
{
    virtual void receiveType(size_t type, char * data) = 0;
    virtual void receiveMsg(size_t type, size_t id, char * data) = 0;

};

The observer should have a method to register for the msgs:

Class Observer
{
void registerForType(type, subscriber);
void registerForMsg(type, id, subscriber);
};

Another Update:

This is really just a rough proof-of-concept. One can do what you want without knowing the exact ancestor chain. Forgive the switching of the trigger and registrationEntry functions (I did it wrong at first, and that was the simplest correction, again proof-of-concept). Another downside of this sketch is that at least a msg has to be constructed to register. If you are looking for a real long term solution, I suggest you find a library, or framework, that has reflection in it already (QT for example has the metaobjects), these could be used to see superclasses. Or, you could use the signals/slots already there.

Output from the code below:

Starting C:\Users\David\Downloads\asdf-build-desktop-Qt_4_8_0_for_Desktop_-MinGW_Qt_SDK__Release\release\asdf.exe...
Base Register
Registration: BaseMsg
Child Register
Registration: Message
Base call
Trigger: BaseMsg
virtual void Subscriber1::newMessage(const BaseMsg&)
Der. call
Trigger: BaseMsg
virtual void Subscriber1::newMessage(const BaseMsg&)
Trigger: Message
virtual void Subscriber2::newMessage(const BaseMsg&)
C:\Users\David\Downloads\asdf-build-desktop-Qt_4_8_0_for_Desktop_-MinGW_Qt_SDK__Release\release\asdf.exe exited with code 0

#include <string>
#include <vector>
#include <map>
#include <stdio.h>
using namespace std;

class BaseMsg
{
public:
    BaseMsg()
    {
        theRealInit();
    }

    //incase you don't want to go all the way down the rabbit hole.
    //At the bottom they are the same
    virtual vector<string> const & registrationEntries() const  {return m_SubClassChain;}
    virtual vector<string> const & triggerEntries() const       {return m_SubClassChain;}

    protected:
    virtual void init() { printf("Should NOT CALL THIS HERE!");}
    vector<string> m_SubClassChain;

private:

    void theRealInit()
    {
        m_SubClassChain.push_back("BaseMsg");
    }


};

class Message : public BaseMsg
{
    public:
    Message() : BaseMsg()
    {
        init(); //MUST BE CALLED from child
    }

    virtual vector<string> const & triggerEntries() const       {return m_TriggerEntries;}

protected:
    virtual void init()
    {
        //BaseMsg::init();
        m_SubClassChain.push_back("Message");
        m_TriggerEntries.push_back("Message");
    }

private:
    vector<string> m_TriggerEntries;
};

class Subscriber
{
public:
    virtual void newMessage(BaseMsg const & i_msg)
    {
        printf("%s\n", __PRETTY_FUNCTION__);
    }
};

class Subscriber2 : public Subscriber
{
public:
    virtual void newMessage(BaseMsg const & i_msg)
    {
        printf("%s\n", __PRETTY_FUNCTION__);
    }
};

class Subscriber1 : public Subscriber
{
public:
    virtual void newMessage(BaseMsg const & i_msg)
    {
        printf("%s\n", __PRETTY_FUNCTION__);
    }
};



class Sender
{
  //typdef vector<Receiver const & > Receivers;
public:

    void registerForMsg(Subscriber * someoneThatCares, BaseMsg const & msg)
    {
        vector<string> const & triggers = msg.triggerEntries();

        vector<string>::const_iterator it = triggers.begin();
        for(; it != triggers.end(); it++)
        {
            printf("Registration: %s\n", it->c_str());

            m_routingMap.insert(pair<string, Subscriber *>(*it, someoneThatCares));
        }
    }


  void send(BaseMsg const & msg)
  {
      vector<string> const & triggers = msg.registrationEntries();
      vector<string>::const_iterator it = triggers.begin();
      for(; it != triggers.end(); it++)
      {

          printf("Trigger: %s\n", it->c_str());
          pair<multimap<string, Subscriber *>::iterator, multimap<string, Subscriber *>::iterator> ret;

          //borrowed from: http://www.cplusplus.com/reference/stl/multimap/equal_range/
          ret = m_routingMap.equal_range(*it);

          multimap<string, Subscriber *>::iterator it1;
          for (it1 = ret.first; it1 != ret.second; ++it1)
          {

              it1->second->newMessage(msg);
          }
      }
  }

private:
  multimap<string, Subscriber *> m_routingMap;
};

int main(int argc, char *argv[])
{
    Sender sndr;

    BaseMsg baseMsg;
    Message message;

    printf("Base Register\n");
    Subscriber1 recv1;
    sndr.registerForMsg(&recv1, baseMsg);

    printf("Child Register\n");
    Subscriber2 recv2;
    sndr.registerForMsg(&recv2, message);


    printf("Base call\n");
    sndr.send(baseMsg); // I want only recv1 to receive this message

    printf("Der. call\n");
    sndr.send(message); // I want both Receivers to receive this message, but only recv2 will receive it

    return 0;
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top