为什么 std::enable_shared_from_this 允许多个 std::shared_ptr 实例?
Why does std::enable_shared_from_this allow multiple std::shared_ptr instances?
有几个问题涵盖了 std::enable_shared_from_this
的行为,但我不认为这是重复的。
从 std::enable_shared_from_this
继承的 类 带有一个 std::weak_ptr
成员。当应用程序创建一个指向[=13=的子类的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
使用相同的控制块,从而避免潜在的崩溃?
就此而言,为什么 shared_from_this
方法在 std::weak_ptr
成员未初始化时失败,而不是仅仅初始化它并返回 std::shared_ptr
?
库的工作方式似乎很奇怪,因为它在很容易成功的情况下失败了。我想知道是否有我不明白的设计considerations/limitations。
我在 C++17 模式下使用 Clang 8.0.0。
为同一个指针创建两个 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);
如果我正确理解你的问题,你会假设第二次调用构造函数 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
突然间,完全有效的代码在没有任何编译器的情况下变得无效 error/warning,因为升级了你的库。应尽可能避免这种情况。
同时,也会造成很多混乱。原因取决于您在 shared_ptr 中输入的 class 的实现,它是已定义或未定义的行为。每次都将其设置为 undefined 会更容易混淆。
如果您没有 shared_ptr
,enable_shared_from_this
是获得 shared_ptr
的标准解决方法。一个 classic 示例:
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);
现在是一个错误。根据 cppreference:shared_ptr 他们特别声明:
std::shared_ptr
is a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr
objects may own the same object.
创建一个实际上不拥有的辅助智能指针(当最后一个副本是 reset/destroyed 时它什么都不做),或者在控制块中携带原始智能指针的副本(在删除对象中) 这样当次级引用计数变为零时主引用计数递减,这是一种非常罕见的情况,并且可能会让大多数程序员感到困惑,但它本身并不是非法的。 (而且我认为在特殊情况下可以为该模式提供强有力的理由。)
另一方面,shared_from_this
的存在强烈表明只有一个人拥有 shared_ptr
,因此当有多组 [=13] 时,可能应该避免拥有 shared_from_this
=] 是预期的。与 std::enable_shared_from_this
.
的隐式行为不同,自引用非拥有指针的显式管理更安全,因为它使此类问题在用户代码中显而易见
有几个问题涵盖了 std::enable_shared_from_this
的行为,但我不认为这是重复的。
std::enable_shared_from_this
继承的 类 带有一个 std::weak_ptr
成员。当应用程序创建一个指向[=13=的子类的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
使用相同的控制块,从而避免潜在的崩溃?
就此而言,为什么 shared_from_this
方法在 std::weak_ptr
成员未初始化时失败,而不是仅仅初始化它并返回 std::shared_ptr
?
库的工作方式似乎很奇怪,因为它在很容易成功的情况下失败了。我想知道是否有我不明白的设计considerations/limitations。
我在 C++17 模式下使用 Clang 8.0.0。
为同一个指针创建两个 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);
如果我正确理解你的问题,你会假设第二次调用构造函数 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
突然间,完全有效的代码在没有任何编译器的情况下变得无效 error/warning,因为升级了你的库。应尽可能避免这种情况。
同时,也会造成很多混乱。原因取决于您在 shared_ptr 中输入的 class 的实现,它是已定义或未定义的行为。每次都将其设置为 undefined 会更容易混淆。
如果您没有shared_ptr
,enable_shared_from_this
是获得 shared_ptr
的标准解决方法。一个 classic 示例:
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);
现在是一个错误。根据 cppreference:shared_ptr 他们特别声明:
std::shared_ptr
is a smart pointer that retains shared ownership of an object through a pointer. Severalshared_ptr
objects may own the same object.
创建一个实际上不拥有的辅助智能指针(当最后一个副本是 reset/destroyed 时它什么都不做),或者在控制块中携带原始智能指针的副本(在删除对象中) 这样当次级引用计数变为零时主引用计数递减,这是一种非常罕见的情况,并且可能会让大多数程序员感到困惑,但它本身并不是非法的。 (而且我认为在特殊情况下可以为该模式提供强有力的理由。)
另一方面,shared_from_this
的存在强烈表明只有一个人拥有 shared_ptr
,因此当有多组 [=13] 时,可能应该避免拥有 shared_from_this
=] 是预期的。与 std::enable_shared_from_this
.