Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using shared_from_this() in constructor

As you know it is not possible to use the std::enable_shared_from_this and shared_from_this() pair from the constructor of an object since a shared_pointer containing the class is not yet in existance. However, I really would like this functionality. I have attempted my own system and it seems to be working OK.

namespace kp
{    

template <class T>
void construct_deleter(T *t)
{
  if(!t->_construct_pself)
  {
    t->~T();
  }

  free(t);
}

template <class T, typename... Params>
std::shared_ptr<T> make_shared(Params&&... args)
{
  std::shared_ptr<T> rtn;
  T *t = (T *)calloc(1, sizeof(T));
  t->_construct_pself = &rtn;
  rtn.reset(t, construct_deleter<T>);
  t = new(t) T(std::forward<Params>(args)...);
  t->_construct_pself = NULL;
  t->_construct_self = rtn;

  return rtn;
}

template <class T>
class enable_shared_from_this
{
public:
  std::shared_ptr<T> *_construct_pself;
  std::weak_ptr<T> _construct_self;

  std::shared_ptr<T> shared_from_this()
  {
    if(_construct_pself)
    {
      return *_construct_pself;
    }
    else
    {
      return _construct_self.lock();
    }
  }
};

}

Can anyone spot any flaws in this logic? I basically use placement new to assign a pointer to the shared_ptr inside the class before the constructor calls.

As it stands I can use it as so:

std::shared_ptr<Employee> emp = kp::make_shared<Employee>("Karsten", 30);

and in the Employee constructor:

Employee::Employee(std::string name, int age)
{
  Dept::addEmployee(shared_from_this());
}

Before I commit this to a relatively large codebase, I would really appreciate some ideas or feedback from you guys.

Thanks!

like image 806
Karsten Pedersen Avatar asked Sep 06 '25 18:09

Karsten Pedersen


1 Answers

I know it's been a while but that might be useful to someone with the same issue : the main problem will happen if you attempt to inherit from a class inheriting your enable_shared_from_this. Especially with this line :

t->_construct_pself = &rtn;

If you have let's say :

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

class Component : public Object {
};

Then the compiler won't be able to cast std::shared_ptr<Component>* to std::shared_ptr<Object>* as for the compiler those types are not related even though Component inherits Object. The easiest solution I see would be to turn _construct_pself to void* like so :

template <class T>
    class enable_shared_from_this
    {
    public:
        void* _construct_pself{ nullptr };
        std::weak_ptr<T> _construct_self;
        std::shared_ptr<T> shared_from_this() const
        {
            if (_construct_pself)
            {
                return *static_cast<std::shared_ptr<T>*>(_construct_pself);
            }
            else
            {
                return _construct_self.lock();
            }
        }
    };

And then do

t->_construct_pself = static_cast<void*>(&rtn);

It's not very sexy and might make other issues arise but it seems to be working...

[EDIT] There is a slightly better and more "C++" alternative, sorry for not thinking about it right away, just do :

t->_construct_pself = reinterpret_cast<decltype(t->_construct_pself)>(&rtn);

[EDIT2] Make shared_from_this const as it does not change anything in the class

[EDIT3] Found an other issue : If you use a copy constructor via make_shared and use operator= inside the constructor before shared_from_this, shared_from_this will return the address of copied object, not of the object's copy. Only solution I see is to define empty copy constructor and assignment operator for enable_shared_from_this and explicitly call the copy constructor from inheriting classes everytime needed... Either that or MAKE SURE you NEVER call operator= before shared_from_this inside your copy constructor.

like image 75
Tab Avatar answered Sep 09 '25 22:09

Tab