Search code examples
c++design-patternsshared-ptrobserver-patterncommand-pattern

Ideas for memory management, std::map, boost::shared_ptr


This question is long so please bear with me.

I'm attempting to solve a dilemma I'm having with memory management, shared pointers, and maps. I just wanted to get some feedback about my architecture, maybe some of ya'll have done it better in the past.

The following examples will be done is pseudo code.

I have a listener class:

class MyListener {
   friend class Command;
   public:
      MyListener() {}
      virtual ~MyListener() {}
      void handleUpdate() {
           std::cout << "Update Handled" << std::endl;
      }
};

Which is to be evoked everytime an object update is called. I'm using a middleware called OpenDDS for interprocess communication framework.

I have a Command class, which inherits a DDS object, and leverages the on_data_received(). When on_data_received() is evoked, I want to call the handleUpdate() method from the class above.

class Command {
   public:
      /*standard constructor destructor here*/
      void on_data_received() {
          m_listener->handleUpdate();
      }
      void write();
   private:
      MyListener *m_listener;
 };

Herein lies the problem. The class that manages this is a Singleton, and uses two methods publish and subscribe to either publish a DDS message or subscribe to one. The subscribe method takes a key value and a raw pointer.

The singleton manages a

std::map<std::string name, Command>

In which a Command class contains the MyListener class.

Here is a snippet of pseudo code that breaks it:

class TaterTotListener : public MyListener {
     void handleCommand() {
         std::cout << "Tater tot found" << std::endl;
     }
};

int main() {
    // make a new smart pointer to the listener
    boost::shared_ptr<TaterTotListener> ttl(new TaterTotListener);
    // tell the singleton we want to publish an object called "TaterTot"
    CommandManager::instance()->publish("TaterTot");
    // tell the singleton we want to subscribe to an object called tater tot
    CommandManager::isntance()->subscribe("TaterTot", ttl.get());

    // processing goes here
    // deallocation

}

Upon deallocation, boost is removing it's ownership of the shared pointer. CommandManager attempts to "clean up" by removing all objects named "TaterTot", but since boost::shared_ptr has already cleaned itself up, a double free memory corruption is thrown. CommandManager singleton is always cleaned up last, so declaring a raw pointer and passing to the subscribe method is going to result in the same behaviour.

Any ideas out there? Did I miss something obvious and intuitive? Am I misunderstanding the usage of shared pointers in this instance?

Any help is greatly appreciated. I'll buy ya'll a beer.


Solution

  • Your design mixes two well-known design patterns, Observer and Command.

    Observer defines a one-to-many dependency between objects so that when one object changes state, all its dependent clients are notified and updated automatically.

    Command encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

    I would recommend studying those patterns (see the links above) and refactor your design to separate the encapsulation of requests from the observation of those requests.