我的std :: vector应该包含指针还是结构?

时间:2011-08-27 21:55:36

标签: c++ memory-management vector reference

我知道持有指针会导致额外的解除引用操作的开销,但它会省去包括(可能很大的)包含我的struct定义的头文件。

然而,我的偏好取决于拥有std::vector<myStruct> *ptr2Vect成员的优势。即,不必在每个元素上调用delete。这有多大的性能优势?矢量真的可以在堆栈上分配对象吗?我对模板类很新,并想知道动态数组是否有可能在堆栈上以及以什么价格扩展?

_ 修改 _

我无法理解默认的复制构造函数和operator =成员,并试图将事物保持为简单的结构。我没有明确定义实现,因此担心将vector元素设为对象而不是指针将在赋值时创建临时对象,这将被破坏,从而破坏其副本。

_ 修改 _

对于延迟提供相关信息感到抱歉(我对代码感到害羞)。

我想调用push_back(newObj)。现在,如果我不使用指针,我有一个很大的问题,我不想执行深度复制,但我的dtor将释放LHS和RHS共享的复制构造函数调用的内存。

7 个答案:

答案 0 :(得分:7)

作为一般的经验法则,我会说你可能不想在你的容器中加上指针,除非有充分的理由。

考虑指针的可能原因:

  • 您有virtual个功能
  • 您有一个类层次结构
  • 您不知道您正在使用它们的对象的大小。 (在这种情况下,您只能使用指针或引用,并且您不能使用引用向量)
  • 你的物品非常大(可能是基准)

不将指针放在容器中的最大原因是它使很多更容易不犯错误并意外泄漏内存。当您开始考虑异常时尤其如此。

在容器中没有指针使得使用STL <algorithms>变得更加容易,请考虑:

#include <vector>
#include <string>
#include <iostream>
#include <iterator>
#include <algorithm>

int main() {
  std::vector<std::string> test;
  test.push_back("hello world");
  std::copy(test.begin(), test.end(), 
            std::ostream_iterator<std::string>(std::cout, "\n"));
}

对战:

#include <vector>
#include <string>
#include <iostream>
#include <iterator>
#include <algorithm>

int main() {
  std::vector<std::string*> test;
  // if push_back throws then this will leak:
  test.push_back(new std::string("hello world"));
  // Can't do:
  std::copy(test.begin(), test.end(), 
            std::ostream_iterator<std::string>(std::cout, "\n"));
  // Will now leak too
}

(我从不做)

或者可能:

#include <vector>
#include <string>
#include <iostream>
#include <iterator>
#include <algorithm>

int main() {
  std::vector<std::string*> test;
  std::string str("hello world");
  test.push_back(&str);
  // Can't do:
  std::copy(test.begin(), test.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
}

但是这个的语义让我感到不舒服 - 根本不清楚delete代码中的其他地方是非常糟糕的事情你仍然无法使用STL算法非常舒服,即使有没有泄密问题。

答案 1 :(得分:3)

指针取消引用的“开销”基本上为零。也就是说,测量它与在其位置引用对象相比将非常困难。要小心early optimization and over-optimization,这是所有编程邪恶的根源。

你应该做任何(指针或对象)最有效的应用程序。

答案 2 :(得分:3)

首先,我同意那些说你编写代码的人,但它最有意义,并且在你的分析工具告诉你之前不要担心这样的微优化。

那就是说,你应该更担心访问你的数据,而不是分配和释放它。如果你对向量元素的访问模式具有良好的局部性 - 例如,遍历它们全部,或者一起访问附近的元素 - 那么指针向量可能会破坏该位置并导致性能受到重大影响。

速度的第一个问题当然是使用好的算法。但是#2的关注点是具有良好的局部性,因为内存很慢......相对于CPU,它每年都会变慢。

因此,对于小而简单的对象,vector<Obj>几乎肯定会比vector<Obj *>更快,并且可能更多更快。

至于“向量是否真的可以在堆栈上分配对象”,答案在语义方面是肯定的,但在实现方面却没有(最有可能)。典型的向量实现由内部三个指针组成:Base,Current和End。所有三个都指向堆上的连续块,向量的析构函数将释放该块。 (同样,这是一个典型的实现;理论上,您的编译器和/或运行时可能会做其他事情。但我敢打赌它不会。)

这种实现通过重新分配该块并复制数据来支持动态扩展。由于两个原因,这并不像听起来那么慢:(1)线性存储器访问(例如复制)非常快; (2)每次重新分配都会通过 factor 增加块的大小,这意味着push_back仍然是O(1)摊销。

答案 3 :(得分:2)

针对指针和结构没有提到的一件事是内存的连续性(更多关于嵌入式的内容)。基本上,struct的向量将在1块内存中分配,而struct的指针向量(可能)将在整个地方分配。因此,内存碎片和数据缓存将严重受损。

答案 4 :(得分:1)

你的问题不是很清楚。首先,你谈论一个指针向量,然后你写一些像:std::vector<myStruct> *ptr2Vect

  1. std::vector<myStruct> *ptr2Vect是指向myStruct个对象的向量的指针。向量不存储指针,因此您不必担心所持对象的内存管理 - 只需确保myStruct是可复制构造的。您需要手动管理清除指向矢量的指针(ptr2Vect
  2. 大多数现代系统使用指针非常有效,如果你提出这类问题,你就会遵循过早优化的路线,停下来,退后一步。
  3. vector依赖于动态分配,但它如何扩展,它管理(你可以控制它到一定程度,例如,如果你事先知道大小,你可以reserve
  4. 从我从问题中收集到的内容,您真的不需要担心自动/动态分配和指针取消引用的开销。这些是您最不关心的问题,只是学会编写好的代码 - 所有其他东西将在以后发布(如果有必要的话)

答案 5 :(得分:0)

  

然而,我的偏好是由拥有std :: vector * ptr2Vect成员的优势决定的。是的,不必在每个元素上调用delete。这有多大的性能优势?

它取决于元素的数量,但它可以为您节省大量的内存和时间。见:

What is the cost of inheritance?

  

vector可以在堆栈上真正分配对象吗?

是肯定的。向量可以为此目的保留内部分配,或者编译器可以在某些情况下优化它。这不是你应该依赖的功能/优化。您可以根据自己的需要创建自己的分配器或pod-array容器。

  

我对模板类相当新,并想知道动态数组是否有可能在堆栈上以及以什么价格扩展?

如果您有一个常量,那么特定的实现(例如boost::array)可以节省大量的运行时开销。我为不同的背景写了几种类型。

答案 6 :(得分:0)

我对你的第一个建议是,'你不必将指针指向矢量'作为成员。你想要一个简单的  矢量myVector;或矢量&lt; myVector;

其次,您将根据以下问题做出决定

  1. 矢量的大小是多少? (最多多少元素) 说n
  2. struct的大小是多少? (的sizeof(T)) 说s
  3. 复制结构的成本是多少? 说c
  4. 你的结构是否持有一些资源? (例如一些文件句柄或信号量和cetra)? 如果它持有一些资源,那么矢量可以使你的生活更加复杂。
  5. 现在n,s,c将确定向量的运行时开销 对于矢量,由于n,s,c导致的成本为零。 对于vector,由n,s,c引起的成本是n * s sizeUnits + n * c executionUnits。

    我自己的经验法则:没有经验法则。首先使用向量编码,如果不够好,则使用向量

    如果您的程序是一个小程序,在您使用了向量后将要退出该过程,那么我甚至不愿意将它们释放出来。 如果没有,那么只需运行一个       for(auto it = v.begin(); it!= v.end(); ++ it)delete * it;