简体   繁体   中英

Memory management in the composite pattern

I am encountering the same problem over and over, in the last weeks. Boiled down to its core, I build a (directed acyclic) hierarchy of objects like:

  • a -> c
  • b -> c
  • b -> d

An instance can have more than one parent, and more than one child. c might be a value shared among readers and writers. b might be a composite value. The hierarchy is easily created by a factory - eg a.setChild(new C()) . Later, a client only cares about the root, on which she calls getValue() or setValue() .

My question is: Who cleans up the hierarchy? Who is responsible to call delete on b or c ?

  1. Option - The factory created the nodes, the factory must delete the nodes: I do not like this option, because I understand a factory as a replacement for new . It feels weird to keep a factory until the instances it created can be destroyed.

  2. Option - "Smart" pointers: Much less good, because it pollutes the interface, and introduces a lot of complexity for a simple thing such as pointers.

  3. Option - A graph-like class that does the memory management: The class collects all nodes a , b , c , d , ... and provides access to the root. The nodes themselves reference each other, but do not delete children, or parents. If the "graph" or composite manager is destroyed, it destroys all nodes.

I prefer the last option. It, however, complicates the construction of the hierarchy. Either you build the hierarchy outside, and tell the graph about every single node, or you build the hierarchy inside the graph, which smells like option 1. The graph can only delete, what it knows. Therefore, memory leaks are somehow in the design, if you have to pass the nodes to the graph.

Is there a pattern for this problem? Which strategy do you prefer?

Edit 1 - Sep 1st, 2014: Sorry, for being unspecific with respect to smart pointers. I tried to avoid yet another "when to use smart pointers question", and instead focus the question on alternative solutions. However, I am willing to use smart pointers, if they indeed are the best option (or if necessary).

In my opinion, the signature setChild(C* child) should be preferred over setChild(std::shared_ptr<C> child) for the same reason as for-each loops should be preferred over iterators. However, I must admit that like std:string a shared pointer is more specific about its semantic.

In terms of complexity, every operation inside a node, now, have to deal with shared pointers. std::vector<C*> becomes std::vector< std::shared_ptr<C> > , ... Furthermore, every pointer carries a reference count around, which could be avoided if there were other options.

I should add that I develop a low level part of a real-time system. It is not a firmware but close.

Edit 2 - Sep 1st, 2014: Thank you for all the input. My specific problem is: I get a byte array with lets say sensor data. At some point, I am told which value is written where in that array. On the one hand, I want to have a mapping from a position in the array to a primitive value (int32, double, ...). On the other hand, I want to merge primitive values to complex types (structures, vectors, ...). Unfortunately, not all mappings are bidirectional. Eg. I can read a comparison between values, but I can not write values according to the comparison result. Therefore, I separated readers and writers and let them, if necessary, access the same value.

Option - "Smart" pointers: Much less good, because it pollutes the interface, and introduces a lot of complexity for a simple thing such as pointers.

Smart pointers are pointers with memory management, which is exactly what you need.

You are not forced to expose the smart pointers in your interface, you can get a raw pointer from the smart one as long as the object is still owned by a smart pointer somewhere (Note that this is not necessarily the best idea though, smart pointers in an interface is far from being an ugly thing).

Actually exposing raw pointers in your interface indirectly introduce much more pollution and complexity because you need to document ownership and lifetime rules which are explicit when using smart pointers, which makes them even more simpler to use than "simple pointers".

Also it is most likely the "futur" way of doing things: since c++11/14 smart pointers are part of the standard as much as std::string , would you say that std::string pollutes the interface and introduces a lot of complexity compared to a const char* ?

If you have performance issues, then the question is: will your hand crafted alternative solution be truly more performant (this need to be measured) and is it worth it in term of development time required to develop the feature and maintain the code ?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM