为什么 std::enable_shared_from_this 允许多个 std::shared_ptr 实例?



有几个问题涵盖了std::enable_shared_from_this的行为,但我不认为这是一个重复的问题。

继承自std::enable_shared_from_this的类带有std::weak_ptr成员。当应用程序创建一个指向std::enable_shared_from_this子类的std::shared_ptr时,std::shared_ptr构造函数会检查std::weak_ptr,如果未初始化,则对其进行初始化并使用std::weak_ptr控制块进行std::shared_ptr。但是,如果std::weak_ptr已初始化,则构造函数只需使用新的控制块创建新std::shared_ptr。这会将应用程序设置为在两个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使用相同的控制块,从而避免潜在的崩溃?

就此而言,为什么该方法在未初始化std::weak_ptr成员时shared_from_this失败,而不仅仅是初始化它并返回std::shared_ptr

库的工作方式似乎很奇怪,因为它在很容易成功的情况下会失败。我想知道是否有我不明白的设计考虑/限制。

我在 C++17 模式下使用 Clang 8.0.0。

如果我正确理解您的问题,您会假设第二次调用构造函数shared_ptr将在逻辑上重用存储在shared_from_this中的控制块。

从你的角度来看,这看起来合乎逻辑。让我们暂时假设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;
};

添加您提到的额外功能确实会在您不需要时添加额外的代码,仅用于检查。不过,这并不是说这很重要,零开销抽象并不是几乎为零的开销抽象。

这不是对问题的回答,而更像是基于用户jvapen对这个问题的回答。

你在回答中说过:

struct C {};
C *p = new C();
std::shared_ptr<C> p1(p);
std::shared_ptr<C> p3(p); // Now a bug

我在这里没有看到的是第 5 行std::shared_ptr<C> p3(p);现在是一个错误。根据cpp偏好:shared_ptr他们特别指出了这一点:

std::shared_ptr是一个智能指针,它通过指针保留对象的共享所有权。多个shared_ptr对象可能拥有同一个对象。

创建两个shared_ptr到同一个指针是未定义的行为,与std::enable_shared_from_this无关。您的代码应该是:

struct C : std::enable_shared_from_this<C> {};
C *p = new C();
std::shared_ptr<C> p1(p);
std::shared_ptr<C> p2 = p->shared_from_this();
std::shared_ptr<C> p3(p1);

创建一个辅助智能指针,该指针要么实际上是不拥有的(当最后一个副本被重置/销毁时它不执行任何操作),要么在控制块(在 deleter 对象中)中携带原始智能指针的副本,以便当辅助引用计数变为零时,主引用计数递减,这种情况非常罕见,可能会让大多数程序员感到困惑, 但它本质上并不是非法的。(我认为在特殊情况下,人们可以为这种模式提供强有力的理由。

另一方面,存在shared_from_this强烈表明只有一个拥有shared_ptr,因此当预期有多个std::shared_ptr集时,可能应该避免拥有shared_from_this。对自引用非拥有指针的显式管理更安全,因为它使此类问题在用户代码中很明显,这与std::enable_shared_from_this的隐式行为不同。

相关内容

  • 没有找到相关文章

最新更新