2

I have a class which inherits from enable_shared_from_this. It has shared_ptrs to child objects and there's one "root" object, so the whole hierarchy is managed by shared_ptr. This way an object can have multiple parents and be destructed safely.

I started writing a constructor, and then I realized the user should be managing the objects using std::shared_ptr like I do internally, and like I saw in some existing libraries, for example gtkmm. So I can do what I see others do: hide the constructor, and write a static member function create() which returns a shared_ptr to the new object. Clearly create() is very useful, because without it I need to call std::make_shared() or, later, std::shared_from_this().

But should I hide the constructor, and why? I can guess some good reasons, for example it enforces the user use the shared_ptr, otherwise the object gets deleted, so it's a guarantee the user doesn't use an "orphan" object which isn't managed by shared_ptr. And it ensures the user doesn't forget to manually create a shared_ptr, because forgetting means the object is deleted, even if it was copied (deep copy, not a copy of a pointer), and then the user notices that very quickly.

Another interesting option is to NOT have the create() static method, and instead have an add_child() method to be the only way to create a new object. This guarantees it's linked to the hierarchy. The problem: flexibility. If someone wants to use an object separately, it's impossible, unless you derive the class.

What would you do/should I do? hide/not hide the ctor? add_child()? create()?

  • 1
    You seem to have the pros and cons figured out pretty well. I would hide the constructor if the class **needs** to be managed by `shared_ptr`. – Drew Dormann Feb 10 '13 at 22:43
  • @DrewDormann In my use case, all objects are in the hierarchy, but in general, I don't know. The only way to tell is experience with shared memory and objects, which I don't have. Libraries like gtkmm hide the ctor - Hold on, I think I got it! There's an excellent reason to hide the ctor: prevent the user from accidentally calling delete on a shared object. With a hidden ctor it can only happen if shared_ptr::get() is called explicitly :) – cfa45ca55111016ee9269f0a52e771 Feb 10 '13 at 22:49
  • But a question remains: make create() public or limit the user to add_child()? – cfa45ca55111016ee9269f0a52e771 Feb 10 '13 at 22:51
  • @fr33domlover if you want to prevent the user from deleting, you hide the destructor, not the constructor. ( you then have to pass it into the shared_ptr on creation, which forces you to provide a function to create said pointers ). – Pete Kirkham Feb 10 '13 at 23:11
  • But the objects do need to be deleted at some point, hiding the destructor is not the issue... – cfa45ca55111016ee9269f0a52e771 Feb 10 '13 at 23:16
  • I think you misunderstand what @PeteKirkham is suggesting. You described an "orphan" issue and his comment would solve that. Deleting via `shared_ptr` would still work. – Drew Dormann Feb 10 '13 at 23:43
  • @DrewDormann Why does deletion through shared_ptr work if the dtor is hidden? Isn't it an access level issue? – cfa45ca55111016ee9269f0a52e771 Feb 10 '13 at 23:46
  • 2
    "you then have to pass [the dtor] into the shared_ptr on creation"... he's saying that you can pass a "deleter" function to the `shared_ptr` constructor. http://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr – Drew Dormann Feb 10 '13 at 23:53
  • By the way, after reading the [example](http://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr) I got the impression the dtor is called anyway, and the Deleter just calls delete on the pointer but doesn't replace the destructor... if judging by the output of the example code – cfa45ca55111016ee9269f0a52e771 Feb 11 '13 at 11:04

1 Answers1

1

In the general case there's no one correct answer. You can start by writing all these methods: constructor, create() and create_child(), all public. Then, while using the interface, preferably while testing it (so that you get it done before you use it in the "real" code, if possible), examine the possible side effects, results and convenience of the different construction options and decide what's best, and what's safe, for your specific use cases.

Of couse you can leave all three of them public if all are safe. Otherwise, hide the unsafe ones by making them private (or maybe protected, if it's a constructor of a class meant to be derived), and leaving the safe ones public for the user to use.