如何实现RAII +延迟初始化

How to implement RAII + lazy initialization?

本文关键字:RAII 延迟 初始化 实现 何实现      更新时间:2023-10-16

是否有可能在c++中实现两者的设计- RAII,确保资源被安全释放,以及-延迟初始化,即资源只有在真正使用时才会被获取。

我的想法是,只是实现作为一个延迟初始化,而在真正的资源获取,使用RAII。

行业惯例如何?

是的,有可能。只要使用std::optional (c++ 17或从Boost)或unique_ptr/shared_ptr

(意见)optional在可读性上有很大的优势——你不能再清楚了,这个值可能没有被初始化。

要显示资源被正确释放:首先让我们从急切初始化(live)开始:

ofstream file("test.txt");
file << "no flushn";
ifstream inFile("test.txt");
string line;
getline(inFile, line);
cout << line << endl;

这个,不为我打印任何东西¹。让我们将写入移到单独的作用域(live):

{
    ofstream file("test.txt");
    file << "no flushn";
}
ifstream inFile("test.txt");
string line;
getline(inFile, line);
cout << line << endl;

应该打印no flush,因为ofstream保证在销毁时将close()文件。(除非test.txt同时被访问)

现在是Boost。可选的惰性init (live):

{
    boost::optional<std::ofstream> file;
    file = ofstream("test.txt");
    file.get() << "no flushn";
}
ifstream inFile("test.txt");
string line;
getline(inFile, line);
cout << line << endl;

资源与常规ofstream同时释放。

¹文件访问不能保证被缓冲,但它提供了一个很好的示例,也可用于在线编译器。

通常的做法是尽可能避免延迟初始化。

如果存在延迟初始化方案,则没有什么可以阻止对象的调用者(或用户)在对象实际初始化之前执行依赖于被初始化的对象的操作。这会导致混乱。

为了处理这个问题,对象(或类)实现需要跟踪对象是否实际初始化。这使得类的实现更加复杂——如果任何成员函数忘记检查对象是否初始化,或者任何成员函数将对象置于无效状态,混乱就会随之而来。如果对象(或类)不这样做,则类更难使用,因为使用该类的代码的任何错误都会导致问题。

相反,更常用的技术是(1)构造函数建立一个不变量(2)成员函数假定维护该不变量,(3)析构函数清理。

换句话说,构造函数初始化对象,成员函数确保对象保持在合理状态。当调用成员函数....时,允许假设对象处于有效状态所以不需要检查。只要所有成员函数在返回时确保对象仍然处于有效状态,就没有问题。

唯一的例外是析构函数,它导致对象不再存在。换句话说,在销毁一个对象(调用它的析构函数)之后,不应该再使用该对象的任何成员。

对于调用者来说,这很简单——在获得创建对象所需的信息之前,不要创建对象。换句话说,不是

 SomeObject object;
 // gather data needed to initialise object
 //   Danger, danger: it is possible to mistakenly use object as if it is initialised here
 object.initialise(needed_data);
 // do other things with object
 //object ceases to exist (e.g. end of {} block).

这样做

 // gather data needed to initialise object
 //   note that the compiler will reject using object here
 SomeObject object(needed_data);
 // do other things with object
 //object ceases to exist (e.g. end of {} block).

在c++中,没有什么可以阻止对象在需要时被创建。变量并不局限于在块的顶部或类似的地方声明。