STL向量的性能

4

STL向量类每次调用push_back时都使用复制构造函数存储对象的副本。这会减慢程序吗?我可以使用自定义的链表类来处理指向对象的指针。虽然它没有STL的一些好处,但仍应该更快。

请看下面的代码:

#include <vector>
#include <iostream>  
#include <cstring>

using namespace std;

class myclass
{
    public:
        char* text;

        myclass(const char* val)
        {
           text = new char[10]; 
           strcpy(text, val);
        }

        myclass(const myclass& v)
        {
            cout << "copy\n";
            //copy data
        }
};

int main()
{
    vector<myclass> list;
    myclass m1("first");
    myclass m2("second");

    cout << "adding first...";
    list.push_back(m1);

    cout << "adding second...";
    list.push_back(m2);

    cout << "returning...";
    myclass& ret1 = list.at(0);
    cout << ret1.text << endl;

    return 0;
}

它的输出如下:

adding first...copy
adding second...copy
copy

输出显示在添加和检索值时都调用了拷贝构造函数。 当我们有更大的对象时,这是否会影响性能呢?

5
这里有一些设计选择是有争议的,但这里有一个不争议的选择:如果你决定想要一个O(1)插入的链表,不要自己编写代码,使用std::list<T>即可。 - j_random_hacker
2
当你在思考 x 是否比 y 更快时,只需尝试并测量它。 - Björn Pollex
11个回答

10

复制会影响性能。如果您在标准容器中保留大型对象,使用智能指针而不是对象本身会是一个好主意。


5

其他帖子中没有提到的一点是,如果您知道向量中需要多少元素,可以使用vector :: reserve预先分配内存区域。如果您使用push_back按值存储对象,这应该会加快速度,因为在循环中达到容量限制时,向量类不必继续重新分配新的连续内存块。


4
是的,它确实有这个功能。这也是我们将在C++0x中引入右值引用的主要原因之一。

3

除非您存储的是基本类型,否则建议使用指针作为向量元素,而不是实际的对象本身。在许多情况下,最好使用智能指针。
拷贝构造函数仍将被调用 - 不是您的类的构造函数,而是智能指针类的构造函数。


1

这完全取决于你的对象有多大。但我认为大部分情况下最好使用值语义(复制整个对象),并且它应该足够高效。这样可以避免你考虑与指针相关的内存管理。

只有当你发现性能不够时,才应该考虑使用指针,然后小心处理内存管理。

如果你需要指针,只需使用vector<myclass*>而不是自己编写集合类。这就是STL通用的美妙之处=)


1
是的,它非常有帮助。我的意思是,并不是一直主张使用指针语义来取代值语义以获得性能优势。使用智能指针仍然需要相对较高的脑力负担与值相比。指针确实具有性能优势,但也意味着共享数据,这会带来复杂性,例如在并发方面。我相信KISS(保持简单和甜美),而过早优化是万恶之源 =) - ryaner
大多数情况下,最好使用值语义(复制整个对象)……等等,士兵。除了内存之外,这仍然是一个坏主意。 - Mr. Boy

1

1

你可能不相信,但是 vector(更好的是 deque)是STL为大多数任务提供的最快的容器。

你可能会担心复制对象,但除非它非常巨大或者复制构造函数有些复杂,否则复制对象的成本要比在堆上分配对象低得多。

堆分配和结果缓存未命中远比简单的复制更加惩罚。

但是让我们不要在空谈中浪费时间:对你的容器进行基准测试,看看哪个容器表现最好,以及它们之间的差距,我敢打赌你会感到惊讶。

如果你的类确实很大或者复制起来很困难,那么总有 boost::ptr_vector,不过除非你使用池,否则显然会破坏缓存局部性 ;)


0

复制会对性能产生一定影响。对于小对象,只需按值存储它们,分析将告诉您是否需要重新考虑。对于较大的对象,您可以在容器中存储智能指针以减轻一些顾虑。同样,如果按值存储对象是自然的方式,请这样做,除非分析表明它是一个重要的瓶颈。

还要注意,我不认为返回实际上正在进行复制。您看到的是“添加第二个”可能会进行两次复制:首先,它必须将向量从1扩展到(可能)2,并将旧元素从其先前位置复制到新位置,然后将元素2复制到其位置。


0

复制对象可能会产生意想不到的副作用。现实是C++类通常不是为了复制而编写的,因此可能会导致错误...理想主义者会说“他们应该正确编写他们的类”,但我更喜欢处理现实,这种情况甚至不会很少发生。 更不用说任何关于创建/销毁的日志记录都会频繁发生。

从根本上说,代码编写者打算发生的并不是复制。这就意味着对于非平凡类型来说,这是一个不好的计划。对于简单情况(比如Point2D类)是可以的,但要小心,一般如果类不携带少量数据,请存储指针(或使用智能指针)。


0

你应该选择一个在你的使用场景下具有最佳性能特征的容器。你的使用场景看起来需要向容器的尾部添加元素。使用 vector 时,如果你事先不知道最大成员数并且无法提前进行 reserve,则有时会发生重新分配和性能损失。

如果你使用 deque,则可以保证在容器的尾部(或头部)插入元素的时间是恒定的,同时仍保留有用的功能,如随机访问。

如果你想要一个语义上是对象容器的东西,仅出于性能原因而将其更改为指针的向量不是我的建议,实际上这可能不是性能增益,因为每个对象都需要单独的内存分配,而使用对象容器可以预先分配多个对象的内存。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接