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