对于c++标准库中的vector,我相信应该是用的比较多的一种结构。读者们对此大多应该也不陌生,但是对于初学者可能仍有一些值得注意的地方,本篇文章将讨论其中一种陷阱。
我们都知道C++类的拷贝有最简单的两种区分:深拷贝和浅拷贝。当我们使用vector进行push_back类的对象时候,对于类的拷贝和析构就应该多加注意。push_back的函数原型为push_back(const value_type& __x),使用的引用,按理来说不会产生拷贝,但是当我们进一步查看push_back时候,会发现有一句语句_M_realloc_insert(end(), __x)。
原来,当我们在使用vector的方法push_back时候,首先会检查容量是否满足,当vector的容量不能满足的时候,会调用_M_realloc_insert(end(), __x),首先将原有数据进行拷贝,增加新的数据,再将原有数据进行析构。因此整个过程会产生类对象的拷贝和析构。(此处的原理不再多做描述,有兴趣的读者可进行搜查)。
当我们push_back的对象不涉及资源(如new,malloc的内存空间,文件描述符等)管理的时候,push_back对原有数据的拷贝和析构看起来无关大雅。但是一但当push_back的对象涉及到了资源管理时,push_back对原有数据的拷贝和析构就会产生很大问题。
话不多说,直接上代码,作为程序员,还是代码能给我们更多信息。头文件如下
#ifndef TEST_H
#define TEST_H
#include <vector>
using namespace std;
/*工具类,其中涉及到了最普遍的资源管理:new*/
class tool_class
{
private:
int* pint;
public:
tool_class();
~tool_class();
void init(int value);
void r_printf();
};
/*任务类,具有工具类tool_class成员变量new_app_class*/
class task_class
{
private:
tool_class new_app_class;
public:
void init(int value);
void exe();
};
/*任务管理类,具有任务数组vector<task_class> v_arrary,存储任务*/
class management_class
{
private:
vector<task_class> v_arrary;
public:
management_class();
void init();
void exe();
};
#endif
源文件代码:
#include <iostream>
#include <string>
#include <tr1/memory>
#include <vector>
#include "test_vector.h"
/*工具类的方法定义*/
tool_class::tool_class()
{
pint = NULL;
}
tool_class::~tool_class()
{
if(pint != NULL)
delete pint;
pint = NULL;
}
void tool_class::init(int value)
{
pint = new int;
*pint = value;
}
void tool_class::r_printf()
{
cout << "r_printf value start"<< endl;
cout << "*pint = "<<*pint<< endl;
}
/*任务类的方法*/
void task_class::init(int value)
{
new_app_class.init(value);
}
void task_class::exe()
{
new_app_class.r_printf();
}
/*管理类的方法*/
management_class::management_class()
{
v_arrary.clear();
}
void management_class::init()
{
//for(int i = 0; i < 3; i++)
//{
task_class tmp;
tmp.init(3);
v_arrary.push_back(tmp);
//}
}
void management_class::exe()
{
for(unsigned int i = 0; i < v_arrary.size(); i++)
{
v_arrary[i].exe();
}
}
int main()
{
management_class management;
management.init();
management.exe();
return 0;
}
管理类对象中我向vector中push_back了一个对象,运行结果如下:
究其原因是,push_back会进行拷贝,使用的是默认的拷贝函数。当定义的局部类对象 task_class tmp;离开作用域时候,会调用析构函数,将new的空间释放。而此时vector中任务类的工具变量任然指向这个空间,当vector离开作用域时候,会再次调用析构释放,造成free double。
而当push_back多个对象时候,情况更加难以想象(读者验证的话,可以在工具类的析构中加打印,同时删除delete,方便查看调用了多少次析构。不然会因free double中断,无法打印真实的析构调用次数)。局部中间变量对像离开作用域的析构,push_back造成的析构,都会给资源管理造成很多麻烦,尤其是当类在析构函数中释放资源时候。
而解决这种麻烦的其中一种方法是,不将类对象push_back进vector,而是将对象的指针保存进vector。vector<task_class *>的形式,其中保存的指针是由new或者malloc开辟的空间,这样数据将在手动free或delete释放之前永久有效。
而push_back造成的拷贝和析构也只是针对保存的对象指针,不涉及到指针指向的内容,就可以保证资源只由代码作者本身手动释放。
当工程代码繁多,应当小心的在类的成员变量中使用其他类。一但类的相关性牵扯较多,代码出现资源管理问题时候就很难定位。上述的方法虽然解决了问题,但是资源的开辟和释放时间之间的差距由任务的量决定,这也容易对代码的性能造成影响。
注:
文章中有错误的地方,欢迎评论区指正。