6

There are classes that I write (often as part of boost::asio) whose objects depend on being wrapped in a shared_ptr because they use shared_from_this(). Is there a way to prevent an object from being compiled if it's not instantiated in a shared_ptr?

So, what I'm looking for:

std::shared_ptr<MyClass> a = std::make_shared<MyClass>(); // should compile fine
std::unique_ptr<MyClass> a = std::make_unique<MyClass>(); // compile error
MyClass a; // compile error
The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • 6
    I second what LRiO said. In general, it is best (if possible) that objects are not self-aware if they are contained in a shared_ptr, or on the heap, or on the stack, or by pointer, or in a vector, or as a data member, or as a global. As soon as they are self-aware of how they are life-cycle managed, they become a lot more constrained. Unnecessarily so. `shared_from_this` is (arguably) an anti-pattern. But... sometimes it may be a necessary anti-pattern. – Eljay Dec 27 '18 at 19:42
  • 1
    Ouch, in particular, at not being able to use a unique pointer. – Tim Randall Dec 27 '18 at 19:53
  • You could initialize the owning smart to this in the ctor with a custom deleter and then arm it latter. – curiousguy Dec 27 '18 at 20:15
  • Is it a base class or a final class? Can it be used as a member of another object (of another, etc.), or an element of container, member of another object, as long as that object has dynamic lifetime and is managed by a smart pointer? – curiousguy Dec 30 '18 at 08:47
  • @TimRandall Not allowing the use of a unique smart ptr is a consequence of wanting to allow client to use weak ptr. – curiousguy Dec 30 '18 at 10:17
  • @curiousguy There's no specific answer to your question because I was asking in general. I have all kinds of cases with me, and when I asked this question (days ago) I wanted to know what my options are and whether there's something I'm missing on how to make this cleaner. – The Quantum Physicist Dec 30 '18 at 11:19
  • Another issue is whether constructors might throw **lately**: can you define a point in the body of `MyClass::MyClass(...)` where construction of the object is definitive, that is no other constructor of a super-object (here the difference between derived classes and classes having `MyClass` as member is insignificant) might throw. – curiousguy Dec 30 '18 at 11:30

1 Answers1

13

Make its constructor private and give it a static factory member function that creates a shared_ptr. Don't forget to document your design decision in a comment!

// Thing that foos the bar
struct Foo : std::enable_shared_from_this<Foo>
{
   // Returns a shared_ptr referring to a new instance of Foo
   static std::shared_ptr<Foo> CreateShared()
   {
      return std::shared_ptr<Foo>(new Foo);
   }

private:
   // To avoid bugs due to the shared_from_this base,
   // we restrict Foo creation to being via CreateShared().
   Foo() = default;
};

(I can't imagine that std::make_shared would work due to the private ctor, but you can try it.)

I've got to say, though, this doesn't sound like the sort of thing a class should be taking responsibility for. It's kind of programming backwards.

To steal Eljay's words:

In general, it is best (if possible) that objects are not self-aware if they are contained in a shared_ptr, or on the heap, or on the stack, or by pointer, or in a vector, or as a data member, or as a global. As soon as they are self-aware of how they are life-cycle managed, they become a lot more constrained. Unnecessarily so. shared_from_this is (arguably) an anti-pattern. But... sometimes it may be a necessary anti-pattern.

I would prefer avoiding enable_shared_from_this and letting people use your Foo however they see fit, such as through a nice lean unique_ptr.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    Related https://stackoverflow.com/questions/8147027/how-do-i-call-stdmake-shared-on-a-class-with-only-protected-or-private-const – Slava Dec 27 '18 at 19:46
  • If you want make shared, add a private token type `struct private_ctor_t { explicit private_ctor_t(int){} };` and a *public* ctor that takes `private_ctor_t` as its first argument. Then call `make_shared(private_ctor_t(0))`. – Yakk - Adam Nevraumont Dec 30 '18 at 01:41
  • @Yakk-AdamNevraumont Suppose. Meh though. Seems a bit leaky. Since the effect of not being able to do `make_unique` is limited to the `CreateShared` impl I think I prefer it how it is tbh – Lightness Races in Orbit Dec 30 '18 at 01:42
  • @light you misunderstand I think; the private ctor type is private. Only way to call the public ctor is with the private ctor type instance. Only way to get that is to be a friend, member *or* have it passed to you. Basically it permits passing the token to make shared *in create shared*, saving a memory allocation and improving locality. This is about make_shared, not make_unique. – Yakk - Adam Nevraumont Dec 30 '18 at 03:59
  • @Yakk-AdamNevraumont Oh, right, that's better than I thought. Still feels a bit leaky with the public ctor taking something you can't create yourself, but it's acceptable to me ^_^ Does it it improve locality though? All it changes is the method of construction, and in a case like this (built-in type args) does that even really serve a purpose? – Lightness Races in Orbit Dec 30 '18 at 16:24
  • @light make shared means the addref on the shared ptr is in memory adjacent to the object; and as shared ptrs are often copied prior to being used, this is better memory locality than them being in completely unrelated locations. – Yakk - Adam Nevraumont Dec 30 '18 at 16:36
  • @Yakk-AdamNevraumont I had no idea make_shared did that. Always thought it was just a way to emplace ctor args and avoid exception-safety issues(ish). Nais. In that case, we should definitely make this class make_shared-capable. – Lightness Races in Orbit Dec 30 '18 at 16:36