与std :: list相比,push_back到std :: vector会创建许多临时对象

时间:2019-02-03 13:53:12

标签: c++ stl

我创建了一个类,用于跟踪具有唯一ID的实例。 构造此类的新实例(或构造它的副本)提供了一个唯一的ID,该ID来自ID池。销毁会将ID放回池中。打印语句观察实例的创建和销毁时间。

#include <iostream>
#include <vector>
#include <list>


class IdPool {
public:
    IdPool()  {
        m_id = allocateID();
        std::cout << "c'tor id: " << m_id << std::endl;
    }

    ~IdPool() {
        freeID(m_id);
        std::cout << "d'tor free id: " << m_id << std::endl;
    }
    IdPool(const IdPool& obj) {
        m_id = allocateID();
        std::cout << "copy c'tor id: " << m_id << std::endl;
    }

    class Init {
    public:
        Init(const int maxIDs) {
            for (int i=maxIDs; i>=1; --i) {
                s_idArray.push_back(i);
            };
        }
    };

    int id() { return m_id; }

private:
    int allocateID() {
        if (s_idArray.empty()) 
            return 0;
        else {
            int id = s_idArray.back();
            s_idArray.pop_back();
            return id;
        }
    }

    bool freeID(int id) {
        if ( (id > 0 ) && (s_idArray.size() < s_maxIdCount) ) {
            s_idArray.push_back(id);
            return true;
        } else {
            return false;
        }
    }

    static std::vector<int> s_idArray;
    static const size_t     s_maxIdCount;
    static Init             s_setIdCount;
    int                     m_id;
};


const size_t IdPool::s_maxIdCount = 10;
std::vector<int> IdPool::s_idArray;
IdPool::Init IdPool::s_setIdCount(IdPool::s_maxIdCount);


int main(int argc, char* argv[]) {
    using namespace std;

    cout << endl << "-- push 2 IDs to list --" << endl;
    list<IdPool> listId;
    for (int i = 0; i < 2; ++i) {
        listId.push_back(IdPool());
        cout << "push_back to list id: " << listId.back().id() << endl << endl;
    }

    cout << endl << "-- push 2 IDs to vector --" << endl;
    vector<IdPool> vecId;
    for (int i = 0; i < 2; ++i) {
        vecId.push_back(IdPool());
        cout << "push_back to vector id: " << vecId.back().id() << endl << endl;
    }

    cout << endl << "-- push 2 IDs to preallocated vector --" << endl;
    vector<IdPool> vecIdReserved;
    vecIdReserved.reserve(5);
    for (int i = 0; i < 2; ++i) {
        vecIdReserved.push_back(IdPool());
        cout << "push_back to reserved vector id: " << vecIdReserved.back().id() << endl << endl;
    }

    return 0;
}

在测试ID生成器类时,我观察到以下行为:

列表:将新ID推入列表可按预期工作(将临时对象的新ID拉出,在临时对象销毁后放回新ID,请参见输出。

向量:将新的ID推到向量上会创建许多临时对象,对应于向量的大小。在每次push_back操作之后,向量的尾部都包含相同的ID,请参见下面的输出。如果我想使用IdPool类标识派生类的实例,则这不是理想的行为。我希望每个push_back操作只有一个临时表。我在这里想念什么?

编辑: 推送到预分配的向量与列表相同。在这种情况下,这可能是解决方法。我只需要记住在使用向量之前先预留即可。

输出

-- push 2 IDs to list --
c'tor id: 1
copy c'tor id: 2
d'tor free id: 1
push_back to list id: 2

c'tor id: 1
copy c'tor id: 3
d'tor free id: 1
push_back to list id: 3


-- push 2 IDs to vector --
c'tor id: 1
copy c'tor id: 4
d'tor free id: 1
push_back to vector id: 4

c'tor id: 1
copy c'tor id: 5
d'tor free id: 4
copy c'tor id: 4
d'tor free id: 1
push_back to vector id: 4


-- push 2 IDs to preallocated vector --
c'tor id: 1
copy c'tor id: 6
d'tor free id: 1
push_back to reserved vector id: 6

c'tor id: 1
copy c'tor id: 7
d'tor free id: 1
push_back to reserved vector id: 7

2 个答案:

答案 0 :(得分:1)

可接受的答案是对为什么看到这么多副本的正确解释,但是有一种消除更多副本的方法:emplace_back。

如您所见,当您编写时:

vecId.push_back(IdPool());

您调用IdPool的默认构造函数,然后调用复制构造函数将其复制到向量的存储器中。 (而且,正如公认的答案所解释,如果必须增加可用存储量,它可能还必须复制矢量存储中已经存在的元素。)

但是如果您要写:

vecId.emplace_back();

新的IdPool将直接在向量的存储区域中构建,节省一个副本。

请记住,vecId的类型为std::vector<IdPool>,因此emplace_back知道需要构造一个IdPool。而且由于我们没有将任何参数传递给emplace_back,因此它知道调用IdPool的默认构造函数。

假设IdPool有一个附加的构造函数,该构造函数的名称为std::string

explicit IdPool(std::string name) { blah, blah, blah }

您还可以使用emplace_back调用此构造函数,如下所示:

vecId.emplace_back("pool party!");

每个元素类型的公共构造函数实际上都有emplace_back的重载。

vector<IdPool> vecId;
for (int i = 0; i < 2; ++i) {
    vecId.emplace_back();
    cout << "push_back to vector id: " << vecId.back().id() << endl << endl;
}

答案 1 :(得分:0)

因为push_back不能按预期工作。 当向量的capacity等于向量的size时,将导致重新分配。但是确切的行为是实现定义的

出于效率考虑,push_back不仅开放了一个仅适合另一个元素的小房间(这违反了C ++标准的时间复杂度要求。),但它申请了一个大尺寸(通常是原始尺寸的两倍) ,这就是为什么您说的尺寸与原始尺寸相对应。)

要减少重新分配的时间并避免额外的重新分配(但实际上,使用保留不能总是避免重新分配),请使用reserve

要缩小向量,请使用shrink_to_fit(但也不能保证一定能成功。)

似乎需要一个example