为啥共享指针在 main 末尾没有超出范围?

Posted

技术标签:

【中文标题】为啥共享指针在 main 末尾没有超出范围?【英文标题】:Why is the shared pointer not going out of scope at end of main?为什么共享指针在 main 末尾没有超出范围? 【发布时间】:2021-10-17 21:14:10 【问题描述】:

我特意在下面的代码中引入了循环依赖。

我的疑问是,当共享指针 w 超出范围时,引用计数不为零,因此 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;

【问题讨论】:

它们彼此处于参考循环中。 您可能混淆了范围和生命周期。两个共享指针都超出范围,但对循环引用执行它们的生命周期并没有结束。 @TanveerBadar - 如果两者都超出范围,则引用计数应为零。因此不会发生内存泄漏。这违反了共享指针引用计数逻辑。你能说得详细一点吗,因为我不明白你的意思。我知道我故意添加了循环依赖。 C++ 标准规定,如果引用计数达到零,则对象被销毁。 因为它不是具有自动存储期限的对象。它是一个非静态数据成员,它与它所属的类的对象一起创建和销毁。考虑一下:Widget* p; p = new Widget; 你不会期望p-&gt;mGadget 在右大括号处被销毁,而*p 还活着,对吗? 好吧,当然,如果这对你有帮助的话。它一开始并没有与任何特定的范围相关联,所以真的没有什么可以摆脱的。 【参考方案1】:

这里有三个std::shared_ptrs:

    wmain mGadget mWidget

后两者是在动态范围内创建的对象的成员。

w 超出了main() 的范围,就像任何其他自动范围的对象一样。但是mGadgetmWidget 被留下了,每个都引用了对方的对象。这两个对象是在动态范围内创建的。

此循环引用可防止对象的最后一个引用超出范围并被破坏。相互保证的僵局。

【讨论】:

假设 'w' 被一些完全独立的堆构造的 Gadget 指向,它没有被释放,那么我们会看到同样的问题吗? w 是自动范围内的对象,它正在引用某个对象。还有谁在引用同一个对象并不重要。这里的两个对象相互引用。结束。 我为什么要对我认为值得回答的问题投反对票? 好的。不知道为什么有人刚刚投反对票。可能有人认为这个问题不值得问。 不幸的是,在 *** 上提出的绝大多数问题质量都很差。这里大约 90% 的问题来自寻找 pleasedomyhomeworkforme.com 或 pleasewritemycodeforme.com 的人。有时人们出于习惯而投反对票。我认为这个问题不应该被否决,所以我采取了相应的行动。【参考方案2】:

您将shared_ptrs 的生命周期与它们引用的对象的生命周期混淆了。

std::shared_ptr 维护一个指向对象的指针,并将该指针与引用计数相关联。 shared_ptr 在引用计数降至 0 之前不会销毁指向的对象。

auto w = std::make_shared&lt;Widget&gt;(); 这会创建一个新的Widget 对象,然后创建一个指向它的新shared_ptr,并将shared_ptr 的引用计数设置为1。

w-&gt;mGadget = std::make_shared&lt;Gadget&gt;(); 这将创建一个新的Gadget 对象,然后创建一个指向它的新shared_ptr,并将shared_ptr 的引用计数设置为1。

w-&gt;mGadget-&gt;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 对象。

【讨论】:

以上是关于为啥共享指针在 main 末尾没有超出范围?的主要内容,如果未能解决你的问题,请参考以下文章

超出范围后,在Lambda中设置共享指针

为啥我会出现超出范围的错误?

为啥在超出数组末尾写入时不会出现分段错误?

python中用find_all查找网页的属性b【0】结果显示超出范围是为啥

为啥我没有得到数组超出范围错误? [复制]

为啥“子字符串(startIndex,endIndex)”没有抛出“超出范围”