有几个问题涉及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
的1}}控制块。但是,如果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
失败了吗?
库的工作方式似乎很奇怪,因为它在很容易成功的情况下会失败。我想知道是否存在我不理解的设计注意事项/限制。
我正在C ++ 17模式下使用Clang 8.0.0。
答案 0 :(得分:5)
如果我正确理解了您的问题,您将假定第二次调用构造函数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;
};
添加您提到的其他功能确实会在不需要时添加额外的代码,仅用于检查。但是,零开销抽象并不是将零开销抽象几乎是那么重要。
答案 1 :(得分:0)
这不是问题的答案,而是更多基于用户对jvapen的回答。
您已在回答中说明了这一点:
src
我在这里看不到的是第5行struct C {};
C *p = new C();
std::shared_ptr<C> p1(p);
std::shared_ptr<C> p3(p); // Now a bug
现在是一个错误。根据{{3}},他们特别声明:
std::shared_ptr<C> p3(p);
是一个智能指针,它通过指针保留对象的共享所有权。几个std::shared_ptr
对象可能拥有相同的对象。
答案 2 :(得分:-1)
为同一指针创建两个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);
答案 3 :(得分:-1)
创建实际上没有所有权的辅助智能指针(在重置/销毁最后一个副本时不执行任何操作),或者在控制块(在Deleter对象中)携带原始智能指针的副本,以便辅助引用计数为零,主要引用计数递减,这是非常罕见的情况,对于大多数程序员来说可能会造成混淆,但这并不是固有的非法行为。 (而且我认为在特殊情况下,可以为这种模式提供充分的理由。)
另一方面,存在shared_from_this
强烈表明只有一个拥有shared_ptr
,因此当多组shared_from_this
时应避免拥有std::shared_ptr
预计。与std::enable_shared_from_this
的隐式行为不同,对自引用非拥有指针的显式管理更安全,因为它使此类问题在用户代码中显而易见。