对向量进行复制构造的实验

experiment with copy construction for a vector

本文关键字:实验 复制 向量      更新时间:2023-10-16

在尝试通过"pass-by-value"进行复制构建和随后的销毁时,我尝试了以下代码:

#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Rock{
int sz;
public:
Rock():sz(0){cout<< "Default ctor"<<endl;}
~Rock(){cout<< "Dtor"<<endl;}
Rock(const Rock& r){ cout << "Copy ctor" << endl; sz = r.sz;}
Rock& operator=(const Rock& r) {cout << "In assignment op" << endl; sz = r.sz;}
};
int main()
{
vector<Rock> rocks;
Rock a, b, c;
rocks.push_back(a);
rocks.push_back(b);
rocks.push_back(c);
return 0;
}

并得到以下输出。到第7行,一切都很好,但我不明白从那时起会发生什么。有人能澄清吗?

Default ctor
Default ctor
Default ctor
Copy ctor
Copy ctor
Copy ctor // all fine I got it...
Dtor
Copy ctor
Copy ctor
Copy ctor
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor

让我们将输出与相应的代码行相关联。

Rock a, b, c;
Default ctor
Default ctor
Default ctor

你可能自己想出来的那个。:-)

rocks.push_back(a);
Copy ctor

再说一遍,你可能想对了。

rocks.push_back(b);
Copy ctor
Copy ctor // all fine I got it...
Dtor

您的评论显然是错误的,因为您几乎可以肯定地没有将两个构造函数调用与以下语句关联起来:-)

结果是,当添加a的副本时,向量只分配了足够的内存来存储a的一个副本(不过,允许分配更多)。因此,它必须分配一个足够大的内存块,以容纳ab的副本,将其存储在旧内存块中的a的副本复制到新内存块,然后复制b,然后在释放(现在不再需要)原始内存块之前销毁a的原始副本。

rocks.push_back(c);
Copy ctor
Copy ctor
Copy ctor
Dtor
Dtor

根据上面的解释,你现在应该能够猜到这里发生了什么。

然而,请注意,如果将另一个元素推回到向量中,很可能再次只得到一个复制构造函数,而没有析构函数调用。这是因为向量的典型策略是在每一步中将分配的内存增加一倍,因此当推回c时,它很可能会为4个对象分配空间。实际上,std::vector需要使用指数策略(但不需要使用因子2,而且有人认为黄金平均数是一个更好的因子)。

}
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor

这里,三个对象cba,然后向量中的三个对象被破坏。

很容易修改代码以打印更多信息:

#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Rock{
    int sz;
public:
    Rock():sz(0){cout<< "Default ctor"<<endl;}
    Rock(int x):sz(x){cout<< "int ctor " << x <<endl;}
    ~Rock(){cout<< "Dtor " << sz <<endl;}
    Rock(const Rock& r){ cout << "Copy ctor from " << r.sz << endl; sz = r.sz;}
    Rock& operator=(const Rock& r) {cout << "Copy ctor " << sz << " from " << r.sz << endl; sz = r.sz;}
};
int main()
{
    vector<Rock> rocks;
    Rock a(1), b(2), c(3);
    rocks.push_back(a);
    rocks.push_back(b);
    rocks.push_back(c);
    return 0;
}

它打印(带说明):

int ctor 1
int ctor 2
int ctor 3
Copy ctor from 1  // copies a into the vector
                  // push_back(a) returns (capacity == 1)
Copy ctor from 1  // vector reallocates to a greater storage
Copy ctor from 2  // copies b into the vector
Dtor 1            // destroy the old elements
                  // push_back(b) returns (capacity == 2)
Copy ctor from 1  // vector reallocates to a greater storage
Copy ctor from 2  
Copy ctor from 3  // copy c into the vector
Dtor 1            // destroy the old elements
Dtor 2
                  // push_back(c) returns (capacity == 4)
Dtor 3            // destroy the local a, b, c
Dtor 2
Dtor 1
Dtor 1            // destroy the vector and its elements
Dtor 2
Dtor 3

每当std::vector必须增加其容量时(因为您正在推送更多元素),它需要分配新的存储,将所有现有元素从旧存储复制到新存储,然后删除旧元素。因此,您将获得对复制构造函数和析构函数的"额外"调用。

如果在每次对push_back的调用之间放入一个cout语句,这将有助于澄清哪些ctor/dtor调用与每个push_back相关联。

但这是我对年表的猜测:

Default ctor
Default ctor
Default ctor
// First push_back() (capacity initially 1)
Copy ctor    // Copy a into vector
// Second push_back() (capacity now grows to 2)
Copy ctor    // Copy rocks[0] to new storage
Copy ctor    // Copy b into vector
Dtor         // Destruct rocks[0] in old storage
// Third push_back() (capacity now grows to 4)
Copy ctor    // Copy rocks[0] to new storage
Copy ctor    // Copy rocks[1] to new storage
Copy ctor    // Copy c into vector
Dtor         // Destruct rocks[0] in old storage
Dtor         // Destruct rocks[1] in old storage
// End of main
Dtor         // Destruct rocks[0]
Dtor         // Destruct rocks[1]
Dtor         // Destruct rocks[2]
Dtor         // Destruct c
Dtor         // Destruct b
Dtor         // Destruct a