【问题标题】:vector push_back calling copy_constructor more than once?向量 push_back 多次调用 copy_constructor?
【发布时间】:2014-11-04 16:46:00
【问题描述】:

我对向量 push_back 的行为方式有点困惑,对于下面的 sn-p,我希望复制构造函数只被调用两次,但输出表明并非如此。是否是向量内部重组导致了这种行为。

输出:

Inside default

Inside copy with my_int = 0

Inside copy with my_int = 0

Inside copy with my_int = 1
class Myint
{
private:
    int my_int;
public:
    Myint() : my_int(0) 
    {
        cout << "Inside default " << endl;
    }
    Myint(const Myint& x) : my_int(x.my_int)
    {
        cout << "Inside copy with my_int = " << x.my_int << endl;
    }

    void set(const int &x)
    {
        my_int = x;
    }
}

vector<Myint> myints;
Myint x;

myints.push_back(x);
x.set(1);
myints.push_back(x);

【问题讨论】:

标签: c++ stdvector


【解决方案1】:

会发生什么:

  1. x 通过push_back 插入。出现一个副本:新创建的元素使用参数初始化。 my_int 被取为零,因为 xs 默认构造函数已将其初始化。

  2. 第二个元素是push_back'd;自从到达the internal capacity 后,向量需要重新分配内存。
    由于没有为Myint1 隐式定义移动构造函数,因此选择了复制构造函数;第一个元素被复制到新分配的内存中(它的my_int 仍然为零......所以复制构造函数将my_int 再次显示为0)然后复制x 以初始化第二个元素(与第 1 步中的第一个。)。这次x 将my_int 设置为1,这就是复制构造函数的输出告诉我们的。

所以总调用次数是三个。这可能因一种实施方式而异,因为初始容量可能不同。但是,最少需要两次调用。

您可以通过提前预留更多内存来减少副本数量 - 即更高的向量容量,这样重新分配就变得不必要了:

myints.reserve(2); // Now two elements can be inserted without reallocation.

此外,您可以在插入时省略副本,如下所示:

myints.emplace_back(0);

这“放置”了一个新元素 - emplace_back 是一个可变参数模板,因此可以采用任意数量的参数,然后将其转发 - 无需复制或移动 - 到元素构造函数。

1 因为有用户声明的拷贝构造函数。

【讨论】:

    【解决方案2】:

    你明白了......这是调整大小。但我只想指出,如果你对构造函数做一些 bean 计数,你可能会对“emplacement”感兴趣:

    #include <iostream>
    #include <vector>
    using namespace std;
    
    class Myint
    {
    private:
        int my_int;
    public:
        explicit Myint(int value = 0) : my_int(value) 
        {
            cout << "Inside default " << endl;
        }
        Myint(const Myint& x) : my_int(x.my_int)
        {
            cout << "Inside copy with my_int = " << x.my_int << endl;
        }
        Myint(const Myint&& x) noexcept : my_int(x.my_int) {
            cout << "Inside move with my_int = " << x.my_int << endl;
        } 
    };
    
    int main() {
        vector<Myint> myints;
        myints.reserve(2);
    
        myints.emplace_back(0);
        myints.emplace_back(1);
    
        // your code goes here
        return 0;
    }
    

    这应该给你:

    Inside default 
    Inside default
    

    而且,由于noexcept on the move constructor...如果你删除reserve,你会得到一个动作,而不是一个副本:

    Inside default 
    Inside default 
    Inside move with my_int = 0
    

    与副本相比,这种移动数据类型没有真正的优势。但从语义上讲,如果您的数据类型更“重量级”并且有一种“移动”其成员的方式更像是将某个指针的所有权转移到大型数据结构,那么这可能会产生很大的不同。

    【讨论】:

      【解决方案3】:

      当向量的大小随着第二个push_back 增加时,必须将向量的现有内容复制到新的缓冲区。为了验证,在第一个push_back之后输出myints.capacity(),应该是1。

      【讨论】:

      • 不相关:为什么this same example 在 OP 的代码中给出输出 0 1 0 而不是 0 0 1?
      • @0x499602D2 在复制旧缓冲区之前,实现可能会将push_back 元素复制到新缓冲区。
      【解决方案4】:

      这取决于为std::vector 类型的对象保留了多少内存。似乎在第一次执行push_back 时,只为一个元素分配了内存。当第二次调用push_back 时,内存被重新分配为第二个元素保留内存。在这种情况下,已经在向量中的元素被复制到新位置。然后也添加了第二个元素。

      您可以自己保留足够的内存来逃避复制构造函数的第二次调用:

      vector<Myint> myints;
      myints.reserve( 2 );
      

      【讨论】:

        【解决方案5】:

        假设复制构造函数的额外调用来自向量的内部重组是正确的。

        更多详情请参阅此答案:https://stackoverflow.com/a/10368636/3708904

        或者这个答案是因为复制构造是必要的:https://stackoverflow.com/a/11166959/3708904

        【讨论】:

          猜你喜欢
          • 2021-11-06
          • 2013-03-28
          • 2021-06-23
          • 2020-11-20
          • 2019-07-04
          • 1970-01-01
          • 2017-03-24
          • 2019-03-22
          相关资源
          最近更新 更多