【问题标题】:Scalar deleting error when deleting std::shared_ptr删除 std::shared_ptr 时出现标量删除错误
【发布时间】:2020-12-23 07:19:00
【问题描述】:

我正在尝试找出如何删除我的 Block 类的子级。我试图用原始指针来做。我不知道为什么,但它没有工作。我收到了标量删除错误。我现在尝试用 std::shared_ptr 来做这件事。我也没有工作。我正在删除孩子:

void Block::remove(Block* block)
    {
        std::shared_ptr<Block> ptr(block);
        auto it = std::find(children.begin(), children.end(), ptr);

        if (it != children.end())
        {
            *it = NULL;
            children.erase(it);
        }
    }

块删除器是:

Block::~Block()
    {
        for (auto& child : this->children)
        {
            child = NULL;
        }


        if (!this->children.empty())
            this->children.clear();
    }

根据调试过程,找到ptr变量,然后删除。在删除所有内容时都运行良好,直到最后一行,我得到了标量删除错误。仅作记录:children 变量的类型为 std::vector&lt;std::shared_ptr&lt;Block&gt;&gt;

编辑: 完整代码在这里:https://github.com/DragonGamesStudios/Ages-of-Life。所有的block函数都定义在AOLGuiLibrary/source/Block.cpp中

【问题讨论】:

  • 从作为参数传入的任何指针创建共享指针可能属于“坏主意”(带有大写字母),但要获得真正的帮助,您应该提供minimal reproducible example。跨度>
  • 你不需要显式的析构函数来销毁std::vector&lt;std::shared_ptr&lt;Block&gt;&gt;。编译器生成的那个会做正确的事。
  • @JaMiT 为什么这是个坏主意?
  • 因为您的代码会在remove() 的末尾有效地调用delete block,@Sherlock。
  • A minimal reproducible example 不应依赖外部网站的链接

标签: c++ oop shared-ptr


【解决方案1】:

您将shared_ptrs 与原始指针混合在一起,这是一种不好的做法。如果我理解正确,您将块存储在共享指针向量中,这意味着该向量对这些对象拥有所有权。当您使用原始指针创建额外的 shared_ptr 时,您会创建另一个拥有同一对象所有权的对象:

std::shared_ptr<Block> ptr(block);

因此,您将尝试删除该对象两次,这会导致未定义的行为。

首先:你需要共享指针吗?考虑使用unique_ptr 作为一种不易出错的想法(如果只有一个指针对象拥有所有权,则更容易理解谁以及何时会破坏底层对象)。

接下来,永远不要在这种情况下使用原始指针:只有在不存储/删除对象时才能使用原始指针。并且肯定不要从已经存储在其中一个中的东西创建智能指针。

【讨论】:

  • shared_ptr 不是为多个所有者设计的吗?
  • 它被设计为从原始指针创建一次(或者使用make_sharedmake_unique 调用更好),然后它只是从unique_ptr 复制/移动/创建。从this 创建共享指针等其他模式更复杂且容易出错。无论如何,您不能将相同的原始指针包装到智能指针中两次。
  • @SherlockHolmes,要使用该语义,您需要复制共享指针本身。如果您只传递一个原始指针,共享指针如何知道是否有其他所有者?原始指针只是一个数字,一个地址。
  • @SherlockHolmes 他们通过 shared_ptr 中的计数器识别。虽然可以通过指针的值唯一地标识对象,但没有标准的解决方案(尽管一些 ECS 系统通过地址来标识对象,但一个广为人知的游戏引擎会这样做,在这种情况下,这样的对象不能是数组或数组的元素)
【解决方案2】:

一旦给shared_ptr 一个指针,shared_ptr 拥有它。您决不能再将该指针指向另一个共享指针。

int * ptr = new int(1234);
{
    std::shared_ptr<int> shp1(ptr); // shp1 owns ptr
    std::shared_ptr<int> shp2(ptr); // shp2 owns ptr ???
}
// at this point ptr has been deleted twice

注意:最好不要使用new,而是调用 std::make_shared 并且一开始就不要提供指向 shared_ptr 的原始指针。

你正在寻找一个孩子正是这个问题。传入一个原始指针,为它创建一个新的所有者。如果该指针是子指针,则它已经拥有。在将原始指针提供给共享指针之后,您不得创建共享指针。

此外,您为 Block 显示的析构函数没有错,但完全没有必要。您在那里编码的所有内容都会自动发生。块被销毁了,它销毁了它持有的向量(所以清除它是不必要的。)向量销毁它的元素,所以它持有的共享指针也被清理了。

【讨论】:

  • @Swift-FridayPie 我同意(因此我在示例下的“注释”),但这是示例代码,以明确同一指针由两个不相关的对象拥有。
【解决方案3】:

如果block 指向数组代码,则此代码是非法的,因为对于new[],您必须调用delete[],而std::shared_ptr&lt;Block&gt; 将调用delete。之后,堆将被损坏,进一步的操作可能会失败。如果blocks 指向数组的一个元素,也会发生同样的情况。如果blocks 指向不在堆中的对象,也会导致错误。

一般来说,删除传递给函数的指针是个坏主意。不能保证指针是由 new 返回的,即使它是:由哪个 new 返回的?

要为数组创建强引用计数器,您必须使用std::shared_ptr&lt;Block[]&gt;,但通常最好使用某种容器。

在共享指针数组中查找指针对象的值:

auto it =  std::find_if( children.begin(), children.end(), [=](auto& el) { 
                return el.get() == block; 
           });

如果children 不是静态的(这不是某种工厂),那么该析构函数代码是完全多余的。在调用~Block() 之后,将调用Block 的每个成员的析构函数,包括将释放其资源并对每个指针调用析构函数的集合,该集合将对每个Block 调用析构函数。

【讨论】:

    猜你喜欢
    • 2021-11-13
    • 2014-09-08
    • 2016-01-03
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 2015-07-08
    • 1970-01-01
    • 2014-01-10
    相关资源
    最近更新 更多