We have a pretty standard tree API using shared pointers that looks roughly like this (implementations omitted for brevity):
class node;
using node_ptr = std::shared_ptr<node>;
class node : public std::enable_shared_from_this<node> {
std::weak_ptr<node> parent;
std::vector<node_ptr> children;
public:
virtual ~node() = default;
virtual void do_something() = 0;
void add_child(node_ptr new_child);
void remove_child(node_ptr child);
node_ptr get_parent();
const std::vector<node_ptr>& get_children();
};
class derived_node : public node {
derived_node() = default;
public:
virtual void do_something() override;
static node_ptr create(/* args... */);
};
// More derived node types...
This works just fine and prevents nodes being leaked as you'd imagine. However, I've read on various other answers on SO that using std::shared_ptr
in a public API like this is considered bad style and should be avoided.
Obviously this ventures into opinion-based territory, so a couple of concrete questions to avoid this question being closed :-)
Are there any well-known pitfalls to using
shared_ptr
s in interfaces like this, which we have so far been fortunate enough to avoid?If so, is there a commonly-used (I hesitate to say "idiomatic") alternative formulation which avoids said pitfalls but still allows for simple memory management for users?
Thanks.
std::shared_ptr
is to manage ownership,so prefer for
print_tree
functionthan
The later requires a
shared_ptr
, so may require a construction of ashared_ptr
from the object. (whereas retrieving object fromshared_ptr
is a simple getter)Now, for your getters, you have mainly the choice between
share_ptr
, if you want to share ownership with userweak_ptr
, secure reference to internalBy secure and insecure, I mean that if object is destroyed, you may test that with
weak_ptr
, but not with simple pointer. The security has some overhead though, so there is a trade-off.If accessors are for local usage only and not to keep reference on it, pointer/reference may be a good option. As example,
std::vector::iterator
are unusable once the vector is destroyed, so there are good for local usage but may be dangerous to keep iterator as reference (but possible).Do you expect/allow that user keeps reference of a node but allow the root (or parent) to be destroyed ? what should happen to that node for user ?
For
void add_child(node_ptr new_child);
, you clearly take ownership. You may hide theshared_ptr
if the node construct it child itself, something like