【问题标题】:Destructing a Single shared_ptr One-by-One逐个销毁单个 shared_ptr
【发布时间】:2015-10-04 00:15:47
【问题描述】:

我试图一个接一个地破坏 shared_ptr,但是当我破坏最后一个指针时, use_count() 变得疯狂。观察我的代码:

#include <iostream>
#include <memory>


int main() {

    int * val = new int(5);
    std::shared_ptr<int> myPtr = std::make_shared<int>(*val);

    myPtr.~__shared_ptr();

}

将在调试器中产生以下输出:

myPtr value: 5  myPtr.use_count():8787448 // Or some other large int

我希望在最终销毁时它将 use_count() 设置为 0 并为整数释放内存。看来这一切都没有发生。

当 use_count() == 1 时,我可以 if() 声明这个,但这似乎很不优雅。有谁知道另一种解决方案?

【问题讨论】:

  • 你在这里所做的一切都是危险的。
  • 如果你想知道指向的对象是否已经被释放,那么只需使用析构函数打印消息的类。你现在手动调用指针的析构函数然后访问它很可能是 UB。
  • 您所看到的并不能证明内存仍然被分配。您可能(并且可能正在)访问已释放的内存并读取其中发生的任何内容,这可能是 int 留下的字节,尚未重新使用。
  • 为什么不让它超出范围并通过第二个shared_ptr检查它的计数?
  • 您为val 中的整数分配的内存在您的示例中泄漏。它不是由共享指针管理的。

标签: c++ pointers shared-ptr smart-pointers explicit-destructor-call


【解决方案1】:

我想我知道你在追求什么。您想检测共享对象的使用计数何时为零。

这样做的方法是使用std::weak_ptr,该std::weak_ptr 旨在std::shared_ptr 一起使用,这样您就可以跟踪对象是否已被销毁。

这里:

#include <memory>
#include <iomanip>
#include <iostream>

int main()
{
    // weak_ptr is only convertable to a shared_ptr if the shared_ptr
    // usage count is > 0
    std::weak_ptr<int> wp;

    {
        std::shared_ptr<int> ptr = std::make_shared<int>(5);
        wp = ptr; // link weak_ptr to shared_ptr

        if(auto sp = wp.lock()) // try to convert weak_ptr to shared_ptr
        {
            // if we get here use_count was > 0
            std::cout << "Before: use count > 0: "
                << std::boolalpha << (sp.use_count() > 0) << '\n';
        }
        else
        {
            // if we get here use_count was == 0
            std::cout << "Before: Destroyed\n";
        }
        // ptr goes out of scope here
    }

    if(auto sp = wp.lock()) // try to convert weak_ptr to shared_ptr
    {
        // if we get here use_count was > 0
        std::cout << "After: use count > 0: "
            << std::boolalpha << (sp.use_count() > 0) << '\n';
    }
    else
    {
        // if we get here use_count was == 0
        std::cout << "After: Destroyed\n";
    }
}

基本上,如果链接的std::shared_ptr 仍然持有对该对象的引用,则关联 std::weak_ptr 可以使用std::weak_ptr::lock 转换为std::shared_ptr。如果失败,则 关联 std::shared_ptr 不再指向共享对象 - 它已被销毁。

【讨论】:

    【解决方案2】:

    我正在尝试逐个销毁 shared_ptr,

    我不知道那是什么意思,但是...

    myPtr.~__shared_ptr();
    

    永远,永远,永远不要这样做。

    您正在为一个自动对象运行析构函数,即一个存在于堆栈上的对象,当它超出范围时将被编译器自动销毁。通过手动销毁它会导致它被销毁两次。一个物体的生命不能结束两次,它不是詹姆斯·邦德,它只存在一次。

    两次销毁同一个对象是未定义的行为。这意味着可能会发生奇怪的事情。问你为什么从一个行为未定义的程序中得到奇怪的结果是浪费时间。当你有未定义的行为时会发生奇怪的事情因为你有未定义的行为。任何事情都有可能发生。你应该庆幸这只是奇怪的,而不是灾难性的。

    该标准在 12.4 [class.dtor] 中特别指出这种确切的情况是未定义的行为:

    一旦为对象调用析构函数,该对象就不再存在;如果 为生命周期已结束的对象调用析构函数(3.8)。 [示例: 如果析构函数 自动对象被显式调用,并且块随后以通常会的方式离开 调用对象的隐式销毁,行为是未定义的。 ——结束示例]

    更糟糕的是,您正在为myPtr 的基类运行析构函数,因此您只是在销毁对象的部分。这意味着您有一个对象,其中一部分已死,一部分还活着,然后在范围结束时,它的一部分又被杀死了。在任何情况下,这都不可能是正确的做法。永远。

    我希望在最终销毁时它将 use_count() 设置为 0 并为整数释放内存。看来这一切都没有发生。

    你的结论是错误的。它可能正在发生,但是如果对象被销毁并且内存被释放,那么试图查看它会产生无意义的结果。你不能问僵尸它的名字是什么,它会回答“BRAINZZZZ!”而不是告诉你任何有用的东西。吃掉你的大脑。现在你死了。不要和僵尸一起玩。

    另外,正如上面的 Neil Kirk cmets,这也是错误的:

    int * val = new int(5);
    std::shared_ptr<int> myPtr = std::make_shared<int>(*val);
    

    您在堆上创建一个int,然后在将由shared_ptr 管理的堆上创建它的副本shared_ptr 拥有一个 int,它的值与 *val 相同,但没有任何东西拥有 val,所以这将是内存泄漏。您可能打算这样做:

    int * val = new int(5);
    std::shared_ptr<int> myPtr(val);
    

    或更可能是这样的:

    int val = 5;
    std::shared_ptr<int> myPtr = std::make_shared<int>(val);
    

    【讨论】:

      猜你喜欢
      • 2011-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-15
      • 2014-11-25
      • 1970-01-01
      • 2023-02-08
      • 1970-01-01
      相关资源
      最近更新 更多