在std :: vector中调整大小与push_back:它是否避免了不必要的复制赋值?

时间:2009-12-14 19:03:39

标签: c++ stl vector

push_back调用方法std::vector时,其大小增加1,意味着创建新实例,然后您传递的参数将被复制到最近创建的元素中,对?例如:

myVector.push_back(MyVectorElement());

那么,如果我想简单地使用其默认值来增加向量的大小,那么使用resize方法不是更好吗?我的意思是这样的:

myVector.resize(myVector.size() + 1);

据我所知,这将完成同样的事情,但会避免完全不必要的元素属性的赋值副本。

这种推理是正确的还是我错过了什么?

11 个答案:

答案 0 :(得分:19)

至少对于GCC来说,使用哪个并不重要(结果如下)。但是,如果你不得不担心它,你应该使用指针或(甚至更好)某种形式的smart pointers.。我当然会推荐the ones in the boost library

如果您想知道在实践中哪个更好用,我建议使用push_backreserve,因为调整大小会在每次调用时调整向量的大小,除非它的大小与要求的尺寸。 push_back和reserve将仅在需要时调整向量的大小。这是一件好事,好像你想要将矢量调整到size+1,它可能已经在size+20,因此调用调整大小不会带来任何好处。

测试代码

#include <iostream>
#include <vector>

class Elem{
    public:
        Elem(){
            std::cout << "Construct\n";
        }
        Elem(const Elem& e){
            std::cout << "Copy\n";
        }
        ~Elem(){
            std::cout << "Destruct\n";
        }   
};


int main(int argc, char* argv[]){
    {
        std::cout << "1\n";
        std::vector<Elem> v;
        v.push_back(Elem());
    }

    {
        std::cout << "\n2\n";
        std::vector<Elem> v;
        v.resize(v.size()+1);
    }
}

测试输出

1
Construct
Copy
Destruct
Destruct

2
Construct
Copy
Destruct
Destruct

答案 1 :(得分:16)

我发现myVector.push_back(MyVectorElement());更直接,更容易阅读。

问题是,resize不仅仅调整了那些地方的数组和默认构造元素的大小;这就是它默认的内容。它实际上需要第二个参数,即每个新元素的副本,默认为T()。从本质上讲,您的两个代码示例完全相同。

答案 2 :(得分:6)

在EA(电子艺界),这被认为是一个很大的问题,他们编写了自己的STL版本,EASTL,其中许多其他内容包括push_back(void) vector } class。

答案 3 :(得分:6)

关于Yacobi接受的答案的测试代码的c ++ 0x观点:

  1. 在课程中添加move构造函数:

    Elem(Elem&& e) { std::cout << "Move\n"; }
    

    使用gcc我得到“移动”而不是“复制”作为push_back的输出,这通常效率更高。

  2. 稍微好一点 emplace运营(采取相同的措施 参数作为构造函数):

    v.emplace_back()

  3. 测试输出:

    1
    Construct
    Destruct
    
    2
    Construct
    Copy
    Destruct
    Destruct
    

答案 4 :(得分:4)

当你执行push_back()时,该方法会检查底层存储区域以查看是否需要空间。如果需要空间,那么它将为所有元素分配一个新的连续区域,并将数据复制到新区域。

但是:新分配的空间的大小不仅仅是一个更大的元素。它使用了一个漂亮的小算法来增加空间(我不认为算法被定义为标准的一部分,但它通常会使分配的空间加倍)。因此,如果您推送大量元素,则只有一小部分元素实际上会导致重新分配基础空间。

要手动实际增加分配空间,您有两个选择:

  • reserve()

    这增加了底层存储空间,而没有向向量添加元素。因此,未来push_back()调用将不太可能需要增加空间。

  • resize()

    这实际上会向向量添加/删除元素以使其大小正确。

  • capacity()

    是否需要重新分配底层存储之前可以存储的元素总数。因此,如果capacity() > size() push_back不会导致重新分配矢量存储。

答案 5 :(得分:3)

你是对的,push_back无法避免至少一个副本,但我认为你担心错误的事情,但resize也不一定会表现得更好(它复制了它的价值)默认为默认构造临时的第二个参数。)

vector不适合复制昂贵的对象。 (几乎)任何push_backresize都可能导致vector的每个当前成员以及任何新成员被复制。

答案 6 :(得分:2)

myVector.resize(myVector.size() + 1);

将调用MyVectorElement的空构造函数。你想达到什么目的?为了在向量中保留空间(并节省内存分配开销),有reserve()方法。你无法避免构造函数。

答案 7 :(得分:2)

当您调用push_back时,假设不需要调整基础存储的大小,vector类将使用“placement new”运算符来就地复制构造新元素。在复制构造之前,向量中的元素将默认构造。

当您致电resize时,几乎会发生完​​全相同的序列。 vector分配存储空间,然后通过将新位置复制到每个新位置来复制默认值。

结构如下:

::new (p) _T1(_Val);

其中p是指向向量存储的指针,_T1是存储在向量中的类型,_Val是“默认值”参数(默认为{{ 1}})。

简而言之,resize和push_back在幕后做同样的事情,速度差异可能是由于多个内部分配,多个数组边界检查和函数调用开销。时间和内存的复杂性是一样的。

答案 8 :(得分:2)

显然你担心效率和性能。

std :: vector实际上是一个非常好的表演者。如果您大致知道它可能会有多大,请使用reserve方法预分配空间。显然,这是以可能浪费的内存为代价的,但如果你经常使用push_back,它会对性能产生很大的影响。

我认为它的实现取决于向量预先保留多少内存(如果有的话),或者在添加元素时保留多少以供将来使用。最糟糕的情况是你所说的 - 一次只能增加一个元素。

在您的应用中尝试进行一些性能测试,无需保留和使用它进行比较。

答案 9 :(得分:1)

push_back :您创建对象并将其复制到向量中 调整大小:向量使用默认构造函数创建对象并将其复制到向量中。

速度差异:您必须测试STL和编译器的实现,但我认为这无关紧要。

答案 10 :(得分:0)

我怀疑实际答案是STL实现和编译器使用的强大功能,但是,“resize”函数具有原型(ref

void resize( size_type num, TYPE val = TYPE() );

表示val是默认构造的,并通过placement new和copy-constructor复制到新分配的(或可能以前分配但未使用的)空间中。因此,这两个操作都需要相同的操作序列:

  1. 调用默认构造函数
  2. 分配空间
  3. 通过复制构造函数初始化
  4. 最好是推迟更清晰,更通用(就STL容器而言)push_back,而不是应用过早优化 - 如果探查器突出显示push_back作为热点,那么最可能的原因是内存分配,即最好通过明智地使用储备来解决。