vector的push_back拷贝构造和空间占用分析

转载自:http://zohead.com/archives/vector-push-back-space-copy/

这两天在实际程序中使用 STL 的 vector push_back 类对象时出现问题,偶尔发现 vector 在 push_back 时的调用类对象的拷贝构造函数和析构函数有点特别,简单做下分析。

程序代码:

cat > test.cpp

#include <iostream>
#include <vector>
 
using namespace std;
 
struct sss
{
public:
    explicit sss(int val) : value(val)
    {
        cout << "---init sss " << this << ", value:" 
        << value << endl;
    }
 
    sss(const sss& org)
    {
        cout << "---copy " << &org << " to " << this << endl;
        value = org.value;
    }
 
    ~sss()
    {
        cout << "---destory sss " << this 
        << ", value:" << value << endl;
    }
  
    int value;
};
 
int main(int argc, char ** argv)
{
    sss s_tmp(11);
    int i = 0;
    vector<sss> vvv;
 
    for (i = 0; i < 5; i++) {
        s_tmp.value++;
        vvv.push_back(s_tmp);
        cout << "size: " << vvv.size() 
        << ", capacity: " << vvv.capacity() << endl;
    }
 
    return 0;
}

功能很简单,main 中定义一个 sss 类对象和对应的 vector,然后在循环中改类成员的值,并依次 push_back 到 vector 中,类的构造函数、析构函数、拷贝构造函数中都加了对应的打印输出。循环运行了5次,往 vector 中增加了5个类成员。

实际运行输出如下:

—init sss 0x22ff20, value:11
—copy 0x22ff20 to 0x5d2a58

size: 1, capacity: 1
—copy 0x5d2a58 to 0x5d2ad8
—copy 0x22ff20 to 0x5d2adc
—destory sss 0x5d2a58, value:12

size: 2, capacity: 2
—copy 0x5d2ad8 to 0x5d2ae8
—copy 0x5d2adc to 0x5d2aec
—copy 0x22ff20 to 0x5d2af0
—destory sss 0x5d2ad8, value:12
—destory sss 0x5d2adc, value:13

size: 3, capacity: 4
—copy 0x22ff20 to 0x5d2af4

size: 4, capacity: 4
—copy 0x5d2ae8 to 0x5d2b00
—copy 0x5d2aec to 0x5d2b04
—copy 0x5d2af0 to 0x5d2b08
—copy 0x5d2af4 to 0x5d2b0c
—copy 0x22ff20 to 0x5d2b10
—destory sss 0x5d2ae8, value:12
—destory sss 0x5d2aec, value:13
—destory sss 0x5d2af0, value:14
—destory sss 0x5d2af4, value:15

size: 5, capacity: 8
—destory sss 0x5d2b00, value:12
—destory sss 0x5d2b04, value:13
—destory sss 0x5d2b08, value:14
—destory sss 0x5d2b0c, value:15
—destory sss 0x5d2b10, value:16
—destory sss 0x22ff20, value:16

结果分析:

vector 每次调用 push_back 时都会拷贝一个新的参数指定的 sss 类对象,这会调用 sss 的拷贝构造函数,第一次的 copy 正常,而且 vector 的实际容量也由 0 变为 1。

第二次调用 push_back,通过输出会发现调用了两次拷贝构造函数,一次析构函数,原来 vector 此时判断容量不够,将容量扩大为原来的两倍,变为 2,并将原来的元素再次拷贝一份存放到新的内存空间,然后拷贝新加的类对象,最后再释放原来的元素。

第三次调用 push_back 时,vector 自动扩大为4,因此拷贝构造函数调用了3次,析构函数调用了2次,程序最终退出了时就析构了 5 次加本身的 sss 类对象一共 6 次。

参考:

由此看来,vector 的 push_back 在发现空间不足时自动将空间以 2 的指数增长:0 -> 1 -> 2 -> 4 -> 8 -> 16 -> 32 …

查找资料后得知,如此设计的主要目的是为了尽可能的减小时间复杂度;如果每次都按实际的大小来增加 vector 的空间,会造成时间复杂度很高,降低 push_back 的速度。

另外关于 push_back 为什么会执行拷贝构造函数,push_back 的原型为:

void push_back(const _Ty& _Val)

参数是以引用方式传递,按说不会拷贝,但 push_back 实际实现中判断空间不足时是调用 insert 函数添加元素:

void push_back(const _Ty& _Val)
{
// insert element at end
if (size() < capacity())
#if _HAS_ITERATOR_DEBUGGING
{
// room at end, construct it there
_Orphan_range(_Mylast, _Mylast);
_Mylast = _Ufill(_Mylast, 1, _Val);
}
#else /* _HAS_ITERATOR_DEBUGGING /
_Mylast = _Ufill(_Mylast, 1, _Val);
#endif /
_HAS_ITERATOR_DEBUGGING */
else
insert(end(), _Val);
}

vs2017

class A
{
public:

	A() {}
	A(const A&)
	{
		std::cout << "construct" << std::endl;
	}
	~A()
	{
		std::cout << "~destroy" << std::endl;
	}
};
int main()
{
	A a1, a2;
	vector<A> v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(a1);
		cout << v.size() << "   " << v.capacity() << endl;
		//空间增加是1,2,3,4,6,9,13,19....
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值