如何实现RAII+延迟初始化?

How to implement RAII + lazy initialization?

是否有可能在 C++ 中实现一个既 - RAII,确保资源被安全释放,并且 - 惰性初始化,只有在真正使用时才获取资源。

我的想法是只实现一个惰性初始化,而在真正的资源获取中,使用RAII。

行业实践如何?

是的,这是可能的。只需使用 std::optional (C++17 or from Boost) or unique_ptr/shared_ptr.

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

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

ofstream file("test.txt");
file << "no flush\n";

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

这不会为我打印任何内容¹。让我们将写入移动到一个单独的范围 (live):

{
    ofstream file("test.txt");
    file << "no flush\n";
}

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

这应该打印 no flush,因为 ofstream 保证 close() 文件在销毁时。 (除非有其他东西同时访问 test.txt

现在使用 Boost.Optional 和惰性初始化 (live):

{
    boost::optional<std::ofstream> file;

    file = ofstream("test.txt");
    file.get() << "no flush\n";
}

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

资源与常规资源同时释放 ofstream

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

通常的做法是尽可能避免惰性初始化。

如果存在惰性初始化方案,则不会阻止对象的调用者(或用户)在实际初始化之前执行依赖于正在初始化的对象的操作。这可能会导致混乱。

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

相反,更常用的技术是 (1) 构造函数建立不变性 (2) 成员函数假定保持不变性 (3) 析构函数清理。

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

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

对于调用者来说,这很容易 - 在创建对象所需的信息可用之前不要创建对象。换句话说,而不是

 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++ 中,没有什么可以阻止在需要时创建对象。变量不限于在块的顶部或类似的地方声明。