问题场景
在一段处理开始时new一个对象, 进行对象的构造或其他处理, 最后返回对象的指针, 但存在对象构造时check不合法的情况,这种情况下返回nullptr, 为避免内存泄露需要在返回nullptr的分支进行delete处理,由于分支可能较多,这里可能会遗漏delete,另外每个分支重复的加delete控制也会让代码看起来很繁琐
代码示例
如下代码示例为从一个文件中读取一个名字和年龄的列表
在ReadPerson函数中有2个check异常情况的分支需要delete掉临时对象,漏掉了delete造成泄露
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
using namespace std;
class Person
{
public:
Person()
{
age = 0;
}
void Load(const string& p)
{
int pos = p.find(',');
if(pos == string::npos || pos == 0)
{
return;
}
name = p.substr(0,pos);
string ageField = p.substr(pos+1);
age = atoi(ageField.c_str());
}
string name;
int age;
};
Person* ReadPerson(const string& line)
{
Person* p = new Person();
p->Load(line);
if(p->name.empty()) //name为空非法
{
return nullptr; //遗漏delete, 造成内存泄露
}
if(p->age < 0) //age < 0 非法
{
return nullptr;
}
return p;
}
void FreePersonList(vector<Person*>& personList)
{
for(auto p : personList)
{
delete p;
}
personList.clear();
}
string ReadFile(const string& filePath)
{
fstream fs;
fs.open("person_list");
if(!fs.good())
{
cout << "open person_list failed!" << endl;
return 0;
}
fs.seekg(0, ios::end);
int length = fs.tellg();
char* contentBuffer = new char[length + 1];
contentBuffer[length] = '\0';
fs.seekg(0, ios::beg);
fs.read(contentBuffer, length);
fs.close();
string content(contentBuffer);
delete[] contentBuffer;
return std::move(content);
}
int main(int argc, char* argv[])
{
string content = ReadFile("person_list");
vector<Person*> personList;
int startPos = 0;
while(true)
{
int pos = content.find('\n', startPos);
if(pos == string::npos)
{
break;
}
string line = content.substr(startPos, pos - startPos);
Person* p = ReadPerson(line);
if(p != nullptr)
{
personList.push_back(p);
}
startPos = pos+1;
}
cout << "person total count:" << personList.size() << endl;
for(auto p : personList)
{
cout << p->name << ", age:" << p->age << endl;
}
FreePersonList(personList);
return 0;
}
文件内容:
程序运行结果:
解决方法
在类似场景考虑使用智能指针, 这里可以考虑使用shared_ptr
主要修改如下函数, 同时删除FreePersonList函数
shared_ptr<Person> ReadPerson(const string& line)
{
shared_ptr<Person> p = make_shared<Person>();
p->Load(line);
if(p->name.empty()) //name为空非法
{
return nullptr; //遗漏delete, 造成内存泄露
}
if(p->age < 0) //age < 0 非法
{
return nullptr;
}
return p;
}
int main(int argc, char* argv[])
{
string content = ReadFile("person_list");
vector<shared_ptr<Person>> personList;
int startPos = 0;
while(true)
{
int pos = content.find('\n', startPos);
if(pos == string::npos)
{
break;
}
string line = content.substr(startPos, pos - startPos);
shared_ptr<Person> p = ReadPerson(line);
if(p != nullptr)
{
personList.push_back(p);
}
startPos = pos+1;
}
cout << "person total count:" << personList.size() << endl;
for(auto p : personList)
{
cout << p->name << ", age:" << p->age << endl;
}
return 0;
}
总结
应该尽量避免在多条件分支中去进行资源回收操作,包括delete, close句柄,或通过专用api来完成释放清理操作的,非常容易遗漏。配合智能指针来用可以规避此类问题