我特意在下面的代码中引入了一个循环依赖项。
我的怀疑是,因为当共享指针w超出范围时,ref计数不为零,因此Widget对象不会被破坏。
但是在main的末尾,共享指针'w->mGadget->mWidget也超出了范围,因为所有的东西都知道在main的末尾不存在了?我对这里的范围有点困惑。我原以为当main退出时,所有实体范围都应该结束。我理解中缺失的环节在哪里?
#include <memory>
#include <iostream>
struct Gadget;
struct Widget
{
std::shared_ptr<Gadget> mGadget;
};
struct Gadget
{
std::shared_ptr<Widget> mWidget;
};
int main()
{
auto w = std::make_shared<Widget>();
w->mGadget = std::make_shared<Gadget>();
w->mGadget->mWidget = w;
return 0;
}
您混淆了shared_ptr
的生存期和它们引用的对象的生存期。
std::shared_ptr
维护一个指向对象的指针,并将该指针与引用计数相关联。shared_ptr
在引用计数下降到0之前不破坏所指向的对象。
-
auto w = std::make_shared<Widget>();
这将创建一个新的Widget
对象,然后创建一个指向它的新shared_ptr
,并将shared_ptr
的引用计数设置为1。 -
w->mGadget = std::make_shared<Gadget>();
这将创建一个新的Gadget
对象,然后创建一个指向它的新shared_ptr
,并将shared_ptr
的引用计数设置为1。 -
w->mGadget->mWidget = w;
这会将w
分配给Gadget
对象的mWidget
成员,从而将Widget
指针的引用计数增加到2。
现在,当main()
退出时,唯一超出作用域的变量是w
,因此这是唯一被销毁的变量,将Widget
指针的引用计数减为1而不是0。由于仍然存在对该指针的活动引用,因此Widget
对象不会被破坏。由于其mGadget
成员具有对Gadget
指针的活动引用,因此Gadget
对象也不会被破坏。
要解决此问题,您需要将Gadget::mWidget
成员改为std::weak_ptr
:
#include <memory>
struct Gadget;
struct Widget
{
std::shared_ptr<Gadget> mGadget;
};
struct Gadget
{
std::weak_ptr<Widget> mWidget; // <-- here
};
int main()
{
auto w = std::make_shared<Widget>();
w->mGadget = std::make_shared<Gadget>();
w->mGadget->mWidget = w;
return 0;
}
std::weak_ptr
不会增加std::shared_ptr
的引用计数(至少在调用std::weak_ptr::lock()
之前),因此Widget
指针的引用计数将是1而不是2。因此,当w
超出范围并被销毁时,Widget
指针的引用计数降至0,销毁Widget
对象,这将销毁其mGadget
成员,这将使Gadget
指针的引用数降至0,从而销毁Gadget
对象。
这里有三个std::shared_ptr
:
main
中的w
mGadget
mWidget
后两者是在动态范围中创建的对象的成员。
w
在main()
中超出了作用域,就像任何其他自动作用域的对象一样。但留下了mGadget
和mWidget
,它们都引用了对方的对象。这两个对象是在动态作用域中创建的。
这个循环引用防止任何一个对象的最后一个引用超出范围并被销毁。相互保证的僵局。