RAII指的是“资源获取就是初始化”(Resource Allocation Is Initialization),它被视作C++中最强大的编程范式之一。 为了理解这一范式的用处,让我们考虑某个函数使用文件句柄时的情况: 不幸的是,随着错误处理机制的引入,事情会变得复杂。 C语言的程序员通常会借助goto语句简化上面的代码: 如果用异常捕获机制来指示错误的话,代码会变得清晰一些,但是仍然有优化的余地。 相比之下,使用C++中的文件流类(fstream)时,fstream会利用自己的析构器来关闭文件句柄。只要离开了某一对象的定义域,它的析构函数就会被自动调用。 与上面几种方式相比,这种方式有着明显的优势: 地道的C++代码应当把RAII的使用扩展到各种类型的资源上,包括:
简单说来,它指的是,用构造函数来获取一个对象的资源,相应的,借助析构函数来释放对象的资源。void doSomethingWithAFile(const char* filename)
{
// 首先,让我们假设一切都会顺利进行。
FILE* fh = fopen(filename, "r"); // 以只读模式打开文件
doSomethingWithTheFile(fh);
doSomethingElseWithIt(fh);
fclose(fh); // 关闭文件句柄
}
假设fopen
函数有可能执行失败,而doSomethingWithTheFile
和doSomethingElseWithIt
会在失败时返回错误代码。
(虽然异常是C++中处理错误的推荐方式,但是某些程序员,尤其是有C语言背景的,并不认可异常捕获机制的作用)。
现在,我们必须检查每个函数调用是否成功执行,并在问题发生的时候关闭文件句柄。bool doSomethingWithAFile(const char* filename)
{
FILE* fh = fopen(filename, "r"); // 以只读模式打开文件
if (fh == nullptr) // 当执行失败是,返回的指针是nullptr
return false; // 向调用者汇报错误
// 假设每个函数会在执行失败时返回false
if (!doSomethingWithTheFile(fh)) {
fclose(fh); // 关闭文件句柄,避免造成内存泄漏。
return false; // 反馈错误
}
if (!doSomethingElseWithIt(fh)) {
fclose(fh); // 关闭文件句柄
return false; // 反馈错误
}
fclose(fh); // 关闭文件句柄
return true; // 指示函数已成功执行
}
bool doSomethingWithAFile(const char* filename)
{
FILE* fh = fopen(filename, "r");
if (fh == nullptr)
return false;
if (!doSomethingWithTheFile(fh))
goto failure;
if (!doSomethingElseWithIt(fh))
goto failure;
fclose(fh); // 关闭文件
return true; // 执行成功
failure:
fclose(fh);
return false; // 反馈错误
}
void doSomethingWithAFile(const char* filename)
{
FILE* fh = fopen(filename, "r"); // 以只读模式打开文件
if (fh == nullptr)
throw std::exception("Could not open the file.");
try {
doSomethingWithTheFile(fh);
doSomethingElseWithIt(fh);
}
catch (...) {
fclose(fh); // 保证出错的时候文件被正确关闭
throw; // 之后,重新抛出这个异常
}
fclose(fh); // 关闭文件
// 所有工作顺利完成
}
void doSomethingWithAFile(const std::string& filename)
{
// ifstream是输入文件流(input file stream)的简称
std::ifstream fh(filename); // 打开一个文件
// 对文件进行一些操作
doSomethingWithTheFile(fh);
doSomethingElseWithIt(fh);
} // 文件已经被析构器自动关闭
只要你正确使用了析构器,就不会因为忘记关闭句柄,造成资源的泄漏。
析构器会在后台关闭文件句柄,不再需要你来操心这些琐事。
无论在函数中的何处拋出异常,都不会阻碍对文件资源的释放。
当它们脱离作用域时,析构器会自动释放其中储存的内容。