Prismatic
Prismatic

Reputation: 3348

Two phase construction to use shared_from_this() during object creation for derived classes

I have a setup with a base class that inherits from enable_shared_from_this

class Object : public enable_shared_from_this<Object>
{ ... };

I inherit from enable_shared_from_this because I need to call shared_from_this() every so often. In certain derivations of Object, I need to call shared_from_this() from with in the constructor, which can't be done:

class Thing : public Object
{
public:
    Thing() : Object(...) 
    { doSomething(shared_from_this()); /* error */ }
};

So a work around is two-phase construction.

class Thing : public Object
{
public:
    Thing() : Object(...) {  }
    void Init() { /* safe to call shared_from_this here */ }
};

A valid way to create a Thing object would be:

shared_ptr<Thing> thing = make_shared<Thing>();
thing->Init();

This is not very nice and error prone but at least it works. However, now there's an issue with further inheritance:

class Bling : public Thing
{
public:
    Bling() : Thing(...) { ... }
    void Init() { /* safe to call shared_from_this here */ }
};

// How do we create a Bling object?
shared_ptr<Bling> bling = make_shared<Bling>();
static_cast<Thing*>(bling.get())->Init(); // horrible
bling->Init();

// Maybe Bling::Init should look like:
// Bling::Init() { Thing::Init(); /* + other stuff */ }
// then we could do:
shared_ptr<Bling> bling = make_shared<Bling>();
bling->Init(); // etc

Is there a safer or cleaner way to do this? For example, API wise its less error prone to make the constructors for Object, Thing and Bling private and use a static Init() function that creates the shared_ptr:

static shared_ptr<Bling> Bling::Init() {
    auto bling = make_shared<Bling>();
    Bling::init(bling);
    return bling;
}

static void Bling::init(shared_ptr<Bling> bling) {
    /* chain parent class init */
    Thing::init(bling); // sig: Thing::init(shared_ptr<Thing>);

    /* do init stuff */
}

// calls Object(), Thing(), Bling(), 
// Object::init(), Thing::init(), Bling::init()
auto bling = Bling::Init();

Basically I'm looking for patterns to implement object creation in a way where I can use a shared_ptr to the object during creation. I need to allow for basic inheritance. I would like suggested methods to consider end-user ease of use and developer maintainability.

Upvotes: 3

Views: 285

Answers (1)

mdr
mdr

Reputation: 197

You probably should not rely on the fact that the user of an object initially always holds it in a shared pointer. Design the class such that objects are allowed on the stack and heap.

How to solve this depends on what you do with the pointer returned by shared_from_this().

The one thing that comes to my mind is that the object registers itself somewhere - i.e. outside of the object. In my opinion it is better to do that outside of the data classes derived from Object. If you have a registry object of class Registry, turn it into a factory (as suggested by David Schwartz):

class Registry;

class Object
{
protected:
    Object() = default;
    friend Registry;
public:
    // ...
};

class Thing : public Object
{
protected:
    Thing() = default;
    friend Registry;
public:
    // ...
};

class Registry
{
    vector<shared_ptr<Object>> store;
public:
    shared_ptr<Thing> CreateThing()
    {
        shared_ptr<Thing> newThing = make_shared<Thing>();
        newThing->init(); // If you like.
        store.push_back(newThing);
        return newThing;
    }
    shared_ptr<Bling> CreateBling();
    // ...
};
  • No need for enable_shared_from_this at all.
  • Easier to understand.
  • Responsibilities clear.
  • Easily changed such that unregistered Object and Thing objects are allowed. (Scratch the protected constructor and the friend declaration.)

It might already be clear, but just to be sure: I am not in favor of enable_shared_from_this. It makes the assumption, that the object is managed by a shared_ptr at the time shared_from_this() is called. If this is not the case, the "behavior is undefined". If at least it would be an exception or nullptr would be returned...

Such assumptions are ok, when the creation of the initial shared_ptr and the call of shared_from_this() are close to each other in the code (whatever that means). Does anyone have a use case for this? I don`t.

Upvotes: 1

Related Questions