C++ 巧用对象辅助内存回收
1 引子
使用c++ 开发时候,肯定会写过在函数内申请多块内存,在退出函数时候释放这些内存这样的逻辑代码。如果函数内部有多个出口(return),若忘记释放,就会出现内存泄漏问题了。本文介绍巧用对象生命周期机制规避函数内容易遗漏释放内存问题。(本文较为基础,熟练c++编程的老手无需阅读本文)
2 事例
来个简单的事例代码,如下:
void test() {
char *p1 = new char[100];
strcpy(p1, "string1");
printf("p1:%s\n", p1);
if (strcmp(p1, "exit") == 0) { // 视意判断,可能是其他逻辑
delete p1;
return;
}
int *p2 = new int;
*p2 = 1;
printf("p2:%d\n", *p2);
if (*p2 == 0) {// 视意判断,可能是其他逻辑
delete p1;
delete p2;
return;
}
long *p3 = new long;
*p3 = 100;
printf("p3:%ld\n", *p3);
if (*p3 == 0) {// 视意判断,可能是其他逻辑
delete p1;
delete p2;
delete p3;
return;
}
// 省略其他会引用到p1,p2,p3 的code
delete p1;
delete p2;
delete p3;
}
上述代码是个说明的例子,无需追究代码实际的意义性,它只是说明函数有多个出口要进行多处delete 而已。
可以看到,上述代码在每个出口都需进行对申请的内存进行释放。如果经过时间长后,代码修改演变,可能会增加/调整其他逻辑的内存申请,这样会比较容易漏掉对内存的释放。如何使得申请的内存,在函数返回前能够不忘记释放?除了编码格外小心外还有没有其他办法?有,可以利用局部对象生命周期的特点来规避这个问题。
3 利用对象生命周期的特点构造内存助手
使用过c++类的程序员都了解对象的生命周期的一些特点:
1)全局对象的生命周期是和程序一致;
2)函数内局部对象生命周期和该函数调用周期一致;
3)对象的创建,必然调用到其构造函数;
4)对象的结束必然会调用到其析构函数。
利用上面的特点,就可以构造一个内存助手来辅助管理内存了。在助手类的构造函数进行内存申请,在其析构函数进行释放。而这个内存助手在函数内使用时候,就可以达到了函数内自动申请/释放内存了,无需担心遗漏对内存的释放。设计代码如下:
// 头文件内
class CRecycleHelper
{
public:
CRecycleHelper(DWORD cbSize);
~CRecycleHelper();
void *GetBuffer();
protected:
char *m_pBuffer;
};
// 源文件内
CRecycleHelper::CRecycleHelper(DWORD cbSize)
{
m_pBuffer = NULL;
if (cbSize > 0) {
m_pBuffer = new char[cbSize];
memset(m_pBuffer, 0, cbSize);
}
}
CRecycleHelper::~CRecycleHelper()
{
if (m_pBuffer != NULL) {
delete []m_pBuffer;
m_pBuffer = NULL;
}
}
void *CRecycleHelper::GetBuffer()
{
return m_pBuffer;
}
4 应用助手改造代码
使用上述内存助手,由助手对象进行内存申请和回收,这样写代码就省心多了,在使用该助手后,上面的测试代码就可以简化为下面样子了:
void test2() {
CRecycleHelper helper1(100 * sizeof(char));
char *p1 = (char *)helper1.GetBuffer();
strcpy(p1, "string1");
printf("p1:%s\n", p1);
if (strcmp(p1, "exit") == 0) {// 视意判断,可能是其他逻辑
return;
}
CRecycleHelper helper2(sizeof(int));
int *p2 = (int *)helper2.GetBuffer();
*p2 = 1;
printf("p2:%d\n", *p2);
if (*p2 == 0) {// 视意判断,可能是其他逻辑
return;
}
CRecycleHelper helper3(sizeof(long));
long *p3 = (long *)helper3.GetBuffer();
*p3 = 100;
printf("p3:%ld\n", *p3);
if (*p3 == 0) {// 视意判断,可能是其他逻辑
return;
}
// 省略其他会引用到p1,p2,p3 的code
}
上面调整后的代码,可以看到函数内没有相应的函数申请和释放了,全部都在助手对象做了处理。如果函数提前退出时候,再也不用担心内存没有释放问题了,而且代码也清爽了很多。使用这个助手进行内存辅助回收,在函数内变量越多/函数逻辑越多时候,优越性就越体现出来了。
5 相关的其他设计
利用局部变量的这些特点,可以写出其他很好玩的工具类。
比如在多线程工程开发时候,访问全局资源时候需要对某个临界区锁进行上锁,在函数退出时候对临界区锁解锁,如果函数出口很多,解锁的地方就很多了,如果忘记解锁是个比较严重问题的。
如果设计一个类似的助手类,在构造函数传入临界区锁进行上锁,在其析构函数时候,对临界区锁进行解锁。这样就不用担心中场退出而忘记解锁问题了。
(全文完)
(欢迎转载本站文章,但请注明作者和出处 云域 – Yuccn )