有几个问题涉及 std::enable_shared_from_this
的行为,但我不认为这是重复的。
继承自std::enable_shared_from_this
的类携带std::weak_ptr
成员。当应用程序创建std::shared_ptr
时指向 std::enable_shared_from_this
的子类,std::shared_ptr
构造函数检查 std::weak_ptr
,如果未初始化,则对其进行初始化并使用 std::weak_ptr
std::shared_ptr
的控制 block 。但是,如果std::weak_ptr
已经初始化,构造函数只是创建一个新的 std::shared_ptr
带有一个新的控制 block 。当两个 std::shared_ptr
之一的引用计数达到时,这会将应用程序设置为崩溃。实例变为零并删除底层对象。
struct C : std::enable_shared_from_this<C> {};
C *p = new C();
std::shared_ptr<C> p1(p);
// Okay, p1 and p2 both have ref count = 2
std::shared_ptr<C> p2 = p->shared_from_this();
// Bad: p3 has ref count 1, and C will be deleted twice
std::shared_ptr<C> p3(p);
我的问题是:为什么图书馆会这样?如果std::shared_ptr
构造函数知道该对象是 std::enable_shared_from_this
子类并费心去检查 std::weak_ptr
字段,为什么它不总是对新的 std::shared_ptr
使用相同的控制 block ,从而避免潜在的崩溃?
就此而言,为什么该方法shared_from_this
当 std::weak_ptr
时失败成员未初始化,而不仅仅是初始化它并返回 std::shared_ptr
?
这个库的工作方式似乎很奇怪,因为它在很容易成功的情况下却失败了。我想知道是否存在我不理解的设计注意事项/限制。
我在 C++17 模式下使用 Clang 8.0.0。
最佳答案
如果我正确理解您的问题,您会认为第二次调用构造函数 shared_ptr
会在逻辑上重用存储在shared_from_this 中的控制 block 。
从您的角度来看,这看起来很合乎逻辑。让我们假设 C
是您正在维护的库的一部分,并且 C
的使用是您的库的用户的一部分。
struct C : std::enable_shared_from_this<C> {};
C *p = new C();
std::shared_ptr<C> p1(p);
std::shared_ptr<C> p3(p); // Valid given your assumption
现在,您找到了一种不再需要 enable_shared_from_this
的方法,并且在库的下一个版本中,该方法将更新为:
struct C {};
C *p = new C();
std::shared_ptr<C> p1(p);
std::shared_ptr<C> p3(p); // Now a bug
突然间,由于升级了库,完全有效的代码变得无效,没有任何编译器错误/警告。应尽可能避免这种情况。
同时,它也会造成很多困惑。因为根据您放入shared_ptr的类的实现,它是已定义的或未定义的行为。每次都将其设置为未定义会更容易混淆。
enable_shared_from_this
是在没有 shared_ptr
的情况下获取 shared_ptr
的标准解决方法。一个经典的例子:
struct C : std::enable_shared_from_this<C>
{
auto func()
{
return std::thread{[c = this->shared_from_this()]{ /*Do something*/ }};
}
NonCopyable nc;
};
添加您提到的额外功能确实会在您不需要时添加额外的代码,只是为了检查。不过,这并不是那么重要,零开销抽象并不是几乎零开销抽象。
关于c++ - 为什么 std::enable_shared_from_this 允许多个 std::shared_ptr 实例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56465544/