【问题标题】:Using shared_from_this() in constructor在构造函数中使用 shared_from_this()
【发布时间】:2016-11-19 00:57:27
【问题描述】:

如您所知,不可能从对象的构造函数中使用 std::enable_shared_from_this 和 shared_from_this() 对,因为包含该类的 shared_pointer 尚不存在。但是,我真的很想要这个功能。我尝试了自己的系统,它似乎工作正常。

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();
    }
  }
};

}

谁能发现这个逻辑中的任何缺陷?在构造函数调用之前,我基本上使用placement new 来分配指向类中shared_ptr 的指针。

就目前而言,我可以这样使用它:

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

在 Employee 构造函数中:

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

在我将它提交到一个相对较大的代码库之前,我非常感谢你们的一些想法或反馈。

谢谢!

【问题讨论】:

  • 您只能通过您的自定义make_shared 创建这些;如果您尝试使用任何其他形式的初始化,它将在运行时失败。只需使用提供创建实例的静态create() 函数的标准模式,然后在返回它之前执行任何需要shared_from_this() 的操作。
  • 抱歉,这种代码不可能通过我团队的审查。你不能想出一个更简单和自我记录的设计吗?您真正试图解决的问题是什么?为什么传统方法不能满足该目标?

标签: c++ memory shared-ptr


【解决方案1】:

我知道这已经有一段时间了,但这可能对遇到相同问题的人有用:如果您尝试从继承您的 enable_shared_from_this 的类继承,则会出现主要问题。 尤其是这一行:

t->_construct_pself = &rtn;

如果你有让我们说:

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

class Component : public Object {
};

那么编译器将无法将std::shared_ptr&lt;Component&gt;* 转换为std::shared_ptr&lt;Object&gt;*,因为编译器这些类型不相关,即使Component 继承Object。 我看到的最简单的解决方案是将_construct_pself 转为void*,如下所示:

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();
            }
        }
    };

然后做

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

这不是很性感,可能会引发其他问题,但它似乎正在工作......

[编辑] 有一个稍微好一点的“C++”替代方案,很抱歉没有马上考虑它,只是这样做:

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

[EDIT2] 将shared_from_this 设为常量,因为它不会更改类中的任何内容

[EDIT3] 发现另一个问题:如果您通过make_shared 使用复制构造函数并在shared_from_this 之前的构造函数中使用operator=shared_from_this 将返回复制对象的地址,而不是对象的副本.我看到的唯一解决方案是为enable_shared_from_this 定义空的复制构造函数和赋值运算符,并在每次需要时从继承类中显式调用复制构造函数……或者确保你永远不要在复制构造函数中的shared_from_this 之前调用operator= .

【讨论】:

    【解决方案2】:

    我认为在构造函数中使用 shared_from_this() 存在语义问题。 问题是当抛出异常时没有有效的对象,但您已经设置了指向它的共享指针。例如:

    Employee::Employee(std::string name, int age)
    {
        Dept::addEmployee(shared_from_this());
        if (...) throw std::runtime_error("...");
    }
    

    现在Dept 将有一个指向该对象的指针,该对象未成功创建。

    【讨论】:

    • 啊,但是如果我们只在构造函数的最后执行 addEmployee 并确保在析构函数的最开始执行 removeEmployee ,那么这也适用于“class Manager : public Employee “继承。然后,它还将确保在 make_shared 调用之后,不完整的 Employee / Manager 永远不会在 Dept 中。
    • 我刚刚注意到,您的代码运行起来有点出人意料:如果在 Employee 的 C'tor 中抛出异常,这将强制调用 D'tor。但是如果 C'tor 抛出,则不应调用 D'tor。
    • 你是对的。虽然我仍然需要释放内存,但我真的应该用“if(!t->_construct_pself)”之类的东西或更好的东西来包围对析构函数的调用。我现在会解决这个问题。谢谢。
    猜你喜欢
    • 2011-03-24
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 2016-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多