使用管理器和向量时出现内存错误

Posted

技术标签:

【中文标题】使用管理器和向量时出现内存错误【英文标题】:Memory error while working with an Manager and an vector 【发布时间】:2018-03-11 00:13:55 【问题描述】:

我想创建一个包含多个对象的管理器,并且必须使用它才能实际创建对象。 对象将它们的信息保存在智能指针中。 这就是我实现它的方式:

struct Object

    std::shared_ptr<int> number;
;

struct Manager

    std::vector<Object> objects;
    Object& createObject()
    
        objects.emplace_back();
        return objects.back();
    
;

int main()

    Manager manager;
    Object& object1 = manager.createObject();

    object1.number = std::make_shared<int>(10);

    for (Object& o : manager.objects)
    
        std::cout << *o.number << std::endl;
    

如果我执行此代码,我会得到预期的输出:10

但是一旦我尝试像这样创建多个对象:

Manager manager;
Object& object1 = manager.createObject();
Object& object2 = manager.createObject();

object1.number = std::make_shared<int>(10);
object2.number = std::make_shared<int>(5);

for (Object& o : manager.objects)

    std::cout << *o.number << std::endl;

我在这个函数的内存库中得到一个运行时错误:

void _Decref()
           // decrement use count
        if (_MT_DECR(_Uses) == 0)
               // destroy managed resource, decrement weak reference count
            _Destroy();
            _Decwref();
            
        

有人知道为什么会这样吗?

【问题讨论】:

你遇到了什么错误? 我的错误代码不是英文,而是在位置 0xDDDDDDE1 写入时出现访问冲突 是的,您不能引用局部变量并在其范围之外使用它。 我必须使用管理器来正确更改对象而不使用引用吗? @nullqube 它是一个局部变量来构造而不是一个局部函数,它可以被引用 【参考方案1】:

将类实例的向量与指向这些类实例的指针或引用结合使用绝不是一个好主意。就像 Bo Persson 已经正确回答的那样,由于 std::vector 的动态特性,这些指针或引用往往会变得悬空:当 std::vector 增长时,它经常将其项目复制到不同的内存位置,留下已经存在的项目引用并且指针无效(悬空)。

您可以通过存储指向类的指针而不是类本身来轻松避免这种情况。

struct Manager

    std::vector<std::unique_ptr<Object>> objects;
    Object& createObject()
    
        objects.emplace_back(std::make_unique<Object>());
        return *objects.back().get();
    
;

现在std::vector 可以随意移动unique_ptr - 智能指针内容(原始指针),因此引用也永远不会改变(当然,除非你故意更改或删除它们)

下面是一个说明,当您使用类实例向量时会发生什么。 灰色的竖条纹象征着内存——这里忽略了内存的真实结构和大小。

第 1 步:您有一个向量(用方括号表示),其中包含一个类实例作为项。向量后面的内存被占用了(现实有点不一样,不过图片应该够用了)

第 2 步:创建指向类实例的引用(或指针)。 (绿色箭头)

第 3 步:将第二个类实例添加到向量中。向量没有空间容纳其项目,因此必须将其内容移动到另一个内存位置。您的指针/参考已损坏! (红色箭头)

下面是指针解决方案的说明:

第 1 步:你又得到了一个向量,但现在它是一个智能指针向量。它拥有一个指向类实例的智能指针(深绿色箭头)。

第 2 步:再次创建指向类实例的引用(或指针)。 (绿色箭头)

第 3 步:将第二个指向类实例的指针添加到向量中。向量没有空间容纳其项目,因此必须将其内容移动到另一个内存位置。但是这次只移动了智能指针,而不是类实例本身!类实例 1 保持原位,智能指针 1 仍指向类实例 1,您的引用保持不变,每个人都保持快乐 :)

另外:除了作为一种安全的解决方案之外,使用指针向量而不是实例向量通常还具有性能优势。 unique_ptr 非常小,几乎总是比它们指向的对象小得多。因此,当std::vector 必须将其项目复制到不同的内存位置时,如果这些只是小的智能指针,它要做的工作就会少得多。 最重要的是,有些类具有昂贵的复制构造函数(例如锁定!)。如果根本不复制类实例,所有这些都可以避免。

【讨论】:

我仍然不清楚指针只保存它指向的东西的内存地址,那么为什么在 std::vector 搞砸之后指针仍然指向正确的内存地址指针指向的对象?还是我理解有误? 请等一下...我只是在创建一个应该澄清这一点的插图。 哦,哇,我忘了只有指针被移动,而不是对象本身。现在它是有道理的。非常感谢您花时间向我解释。【参考方案2】:

向向量中添加新元素时,您可能会面临对旧对象的引用失效的风险。

查看http://en.cppreference.com/w/cpp/container/vector/emplace_back 上面写的:

如果新的size() 大于capacity(),则所有迭代器和引用(包括过去的迭代器)都将失效。

所以添加object2后,引用object1可能无效。

【讨论】:

谢谢,通过使用 vector::reserve 我能够增加容量并修复了我的错误。 哦,不......这绝对没有解决你的错误,Dolfos......它只是将它推迟到以后 - 当向量再次扩展时;)请查看我的真实答案修复。 @user2328447 你是对的,谢谢,我不完全明白为什么它与你的解决方案一起工作,我想我还有一些功课要做;) 或许我可以把答案延伸一点,让原理更清晰……我试试看。 @Dolfos,我写了一篇文章解释了它是如何工作的:C++ std::vector for post-beginners。我认为你会从阅读这一切中受益,但至少请查看“错误警报!”部分。我会帮忙的。

以上是关于使用管理器和向量时出现内存错误的主要内容,如果未能解决你的问题,请参考以下文章

ruby 版本管理器(安装 rvm 时出现以下内存错误)

使用共享内存时出现分段错误

使用 Firebase 时出现内存不足错误

电脑关机时出现内存错误

使用 QNetworkManager 时出现内存访问错误

访问共享内存时出现分段错误