一、new/delete
在c++中新增了new/delete关键字,当我们定义了一个类Class Test;时,auto test = new TestNew() 这条语句将会执行Test的构造函数默认构造函数,例如:
class TestNew
{
public:
TestNew()
{
_i = 0;
_c = 0;
std::cout << _i << _c;
}
~TestNew()
{
}
private:
int _i;
float _c;
};
int main()
{
auto test_new = new TestNew();
return 0;
}
实际上 new和delete操作都分为两步,new 操作首先 malloc sizeof(TestNew)大小的内存空间,再去执行TestNew()构造函数。同时delete test_new 操作是先执行~TestNew()操作,再执行free(test_new)操作。 所以导致了一些问题的出现,举例如下:
class TestNew
{
public:
TestNew()
{
_i = new int[10];
_c = 0;
std::cout << "TestNew()" << std::endl;
}
~TestNew()
{
if (_i != nullptr)
{
delete[] _i;
_i = nullptr;
}
std::cout << "~TestNew()" << std::endl;
}
private:
int *_i;
float _c;
};
int main()
{
void *p = new TestNew();
delete p;
return 0;
}
运行结果:
该代码用void* 指针去指向 new TestNew(), 导致调用的构造函数,而没有调用析构函数,同时构造函数中分配的内存,在析构函数中没有正确执行,导致了内存泄漏。
二、常见用法
而如下结构是经常用的:
struct Data
{
~Data()
{
delete _data;
_size = 0;
}
void *_data; // 指针
int _size; // 大小
};
template<class T>
void AddData(std::list<Data*> &list_data)
{
void* t = new T();
Data *data = new Data();
data._data = t;
data._size = sizeof(T);
list_data.push_back(data);
}
int main()
{
std::list<Data*> list_data;
AddData<TestNew>(list_data);
AddData<int>(list_data);
AddData<double>(list_data);
for (auto iter = list_data.begin(); iter != list_data.end(); iter++)
{
delete *iter;
}
return 0;
}
实现一个可以增加任何数据的list,此时int和double是可以正常释放的,而TestNew是不能释放成功,能够正常释放的空间只有
sizeof(TestNew);如何解决这个问题成为不定参数列表的关键。事实上stl中的内存分配都不通过new/delete来进行,因为这本来就两步操作,stl分配方式如下:
- allocator与类绑定,因为allocator是一个泛型类
- allocate()申请指定大小空间
- construct()构建对象,其参数为可变参数,所以可以选择匹配的构造函数
- 使用,与其它指针使用无异
- destroy()析构对象,此时空间还是可以使用
- deallocate()回收空间
虽然增加了一步,但大体流程还是先分配内存,再构造对象。
三、解决方案(2019.6.26增加)
既然是因为无法调用到析构函数导致内存泄漏,所有我们可以采取手动去调用析构函数的方法,定义一个泛型类,包装需要构造的函数,同时增加一个头指针,指向析构函数,在消耗这个对象时,可以先手动执行析构函数,再释放内存(具体实现待添加...)
template<class T>
struct data_construct_child;
struct data_construct
{
template<class T>
static int size()
{
return sizeof(T) + sizeof(void*);
}
template<class T>
static void* construct(const T& data, void *memory = nullptr)
{
if (memory == nullptr) memory = new char[size<T>()];
*((void **)memory) = data_construct_child<T>::get_instance();
char *p_data = (char*)memory + sizeof(void*);
new (p_data) T(data);
return (void*)memory;
}
template<class T>
static T& get_value(void *p)
{
T *t = (T*)((char*)p + sizeof(void*));
return *t;
}
static void destruct(void *memory, bool delete_memory = true)
{
data_construct *constructs = (data_construct *)*((void**)memory);
constructs->call_destruct((char*)memory + sizeof(void*));
if (delete_memory) delete[] memory;
}
virtual void call_destruct(void *p) = 0;
};
template<class T>
struct data_construct_child : public data_construct
{
static data_construct_child<T> * get_instance()
{
static data_construct_child<T> instance;
return &instance;
}
virtual void call_destruct(void *p)
{
T *t = (T*)p;
t->~T();
}
};
class TestNew
{
public:
TestNew()
{
_i = new int[10];
_c = 0;
std::cout << "TestNew()" << std::endl;
}
TestNew(const TestNew & test_new)
{
_c = test_new._c;
_i = new int[10];
for (int i = 0; i < 10; i++)
{
_i[i] = test_new._i[i];
}
std::cout << "TestNew(TestNew &t)" << std::endl;
}
~TestNew()
{
if (_i != nullptr)
{
delete[] _i;
_i = nullptr;
}
std::cout << "~TestNew()" << std::endl;
}
private:
int *_i;
float _c;
};
struct Data
{
~Data()
{
data_construct::destruct(_data);
_size = 0;
}
void *_data; // 指针
int _size; // 大小
};
template<class T>
void AddData(std::list<Data*> &list_data)
{
T t;
void* pp = data_construct::construct<T>(t);
auto data = new Data();
data->_data = pp;
data->_size = data_construct::size<T>();
list_data.push_back(data);
}
int main()
{
std::list<Data*> list_data;
AddData<TestNew>(list_data);
AddData<int>(list_data);
AddData<double>(list_data);
for (auto iter = list_data.begin(); iter != list_data.end(); iter++)
{
delete *iter;
}
return 0;
}
通过一个模板类,在封装分配内存和执行构造、析构函数两步,达到完美内存释放的目的
运行结果如下:
从结果上看,已经实现调用了析构函数,再删除内存,这两步,同时也可以只调用析构函数不删除对象内存。