【问题标题】:push_back objects into vector memory issue C++push_back 对象到向量内存问题 C++
【发布时间】:2023-03-04 21:14:01
【问题描述】:

这里比较两种初始化对象向量的方式。

1.
    vector<Obj> someVector;
    Obj new_obj;
    someVector.push_back(new_obj);

2.
    vector<Obj*> ptrVector;
    Obj* objptr = new Obj();
    ptrVector.push_back(objptr);

第一个push_back是实际对象而不是对象的指针。向量 push_back 是否复制了被推送的值?我的问题是,我有很大的对象和很长的向量,所以我需要找到一种节省内存的最佳方法。

  • 第二种方式更好吗?
  • 是否有其他方法可以获得对象/指针向量,以便以后找到每个对象并同时使用最少的内存?

【问题讨论】:

  • 如果可以的话,它会在 C++11 中移动它,第一个是尝试推送一个函数。
  • @chris 我需要在代码中明确写什么才能让它移动而不是复制?还是使用 C++11 指定编译?或者我不需要写任何东西,它只是为我做的?我的意思是第一个构造函数。
  • 我很好奇;你的类的实例有多“巨大”?
  • @LoganYang, -std=c++11 是你所需要的。 push_back 有一个采用右值引用的重载(如果您像这样单独制作对象并且在不再使用该对象之前不要使用 std::move 之类的东西来传递它,则不会使用它),还有emplace_back.

标签: c++ memory vector


【解决方案1】:

在上述两个选项中,不包括的第三种是最有效的:

std::vector<Obj> someVector;
someVector.reserve(preCalculatedSize);
for (int i = 0; i < preCalculatedSize; ++i)
  someVector.emplace_back();

emplace_back 直接将对象构造到vector 为其安排的内存中。如果您在使用前reserve,可以避免重新分配和移动。

但是,如果对象真的很大,那么缓存一致性的优势就更少了。所以vector 的智能指针是有意义的。因此第四个选项:

std::vector< std::unique_ptr<Obj> > someVector;
std::unique_ptr<Obj> element( new Obj );
someVector.push_back( std::move(element) );

可能是最好的。在这里,我们表示数据的生命周期以及如何在几乎零开销的同一结构中访问数据,从而防止数据不同步。

当你想移动它时,你必须明确地std::move std::unique_ptr。如果您出于某种原因需要原始指针,.get() 是访问它的方法。 -&gt;*explicit operator bool 都被覆盖了,所以只有当你有一个需要 Obj* 的接口时,你才真正需要调用 .get()

这两种解决方案都需要 C++11。如果您缺少 C++11,并且对象确实很大,那么“指向数据的指针向量”是可以接受的。

无论如何,您真正应该做的是确定哪个与您的模型最匹配,检查性能,并且只有在存在实际性能问题时才进行优化。

【讨论】:

  • 我建议“指向数据的智能指针向量”,而不是“指向数据的指针向量”
【解决方案2】:

如果您的Obj 类不需要多态行为,那么最好将Obj 类型直接存储在vector&lt;Obj&gt; 中。

如果您将对象存储在vector&lt;Obj*&gt; 中,那么您将承担在不再需要这些对象时手动解除分配这些对象的责任。在这种情况下,如果可能的话,最好使用vector&lt;std::unique_ptr&lt;Obj&gt;&gt;,但同样,只有在需要多态行为时。

vector 会将Obj 对象存储在堆上(默认情况下,除非您在vector 模板中覆盖allocator)。这些对象将存储在连续的内存中,这也可以为您提供更好的缓存局部性,具体取决于您的用例。

使用vector&lt;Obj&gt; 的缺点是从vector 频繁插入/删除可能会导致Obj 对象的重新分配和复制。但是,这通常不会成为您的应用程序的瓶颈,如果您觉得是,您应该对其进行分析。

使用 C++11 move semantics,可以大大减少复制的影响。

【讨论】:

  • 我认为 OP 担心副本本身。这里的任何答案都应该提到移动语义以及如果使用指针,您现在必须自己释放每个元素。
【解决方案3】:

如果您可以提前保留大小,使用vector&lt;Obj&gt; 将占用更少的内存来存储。如果不必重新分配向量,vector&lt;Obj *&gt; 必然会使用比vector&lt;Obj&gt; 更多的内存,因为您有指针的开销和动态内存分配的开销。如果您只有几个大对象,则此开销可能相对较小。

但是,如果您即将耗尽内存,使用 vector&lt;Obj&gt; 可能会导致问题,如果您无法提前保留正确的大小,因为在重新分配向量时您将暂时需要额外的存储空间。

拥有大量大型对象的向量也可能导致内存碎片问题。如果您可以在程序执行的早期创建向量并保留大小,这可能不是问题,但如果稍后创建向量,您可能会因为堆上的内存漏洞而遇到问题。

【讨论】:

  • 那么我会坚持首选。谢谢!
【解决方案4】:

在这种情况下,我会考虑第三种可能性:使用std::deque 而不是std::vector

这是您给出的两者之间的中间点。 vector&lt;obj&gt; 分配一个巨大的块来保存向量中对象的所有实例。 vector&lt;obj *&gt; 分配一个指针块,但对象的每个实例都在它自己的块中。因此,您得到 N 个对象和 N 个指针。

双端队列会创建一个指针块和多个对象块——但(至少通常情况下)它会将多个对象(称为 M)放在一个块中,所以你会得到一个块N/M 个指针和 N/M 个对象。

这避免了对象向量或指针向量的许多缺点。一旦分配了一个对象块,就不必重新分配或复制它们。您确实(或可能)最终必须重新分配指针块,但如果您尝试手动分配,它将比指针向量小(M 倍)。

一个警告:如果您使用的是 Microsoft 的编译器/标准库,这可能效果不佳 - 他们有一些 strange logic(在 VS 2013 RC 中仍然存在)这意味着如果您的对象大小大于 16 ,每个块你只会得到一个对象 - 即,相当于你的 vector&lt;obj *&gt; 想法。

【讨论】:

    猜你喜欢
    • 2011-06-09
    • 2013-03-01
    • 2014-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多