Reputation: 6415
Is it possible to implement in C++ a design that is both - RAII, to ensure the resource is safely released, and - lazy initialzation, that the resource is acquired only when it's really used.
My idea is that just implement as a lazy initialization, while in the real resource acquisition, use RAII.
How's the industry practice?
Upvotes: 0
Views: 1549
Reputation: 36607
The common practice is to avoid lazy initialisation where at all possible.
If there is a lazy initialisation scheme, there is nothing preventing the caller (or user) of the object from doing something that relies on the object being initialised, before it is actually initialised. That can result in chaos.
To cope with that, the object (or class) implementation needs to keep track of whether an object is actually initialised or not. That makes the class implementation more complicated - if ANY member function forgets to check if the object is initialised, or if any member function places the object in an invalid state, chaos can ensue. If the object (or class) does not do that, the class is harder to use, because any mistake by code which uses the class causes problems.
Instead, the technique more usually used is that (1) constructors establish an invariant (2) member functions assume maintain that invariant and (3) destructors clean up.
In other words, the constructors initialise the object, member functions ensure the object stays in a sensible state. The member functions are allowed to ASSUME the object is in a valid state when they are called .... so don't need to check. As long as all member functions ensure the object is still in a valid state when they return, there is no problem.
The only exception is the destructor, which causes the object to cease to exist. In other words, after destroying an object (invoking its destructor) no members of that object should be used at all.
For the caller, it is easy - don't create an object until information needed to create it is available. In other words, instead of
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).
do this
// 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).
In C++, there is nothing preventing an object being created when it is needed. Variables are not limited to being declared at the top of a block or anything like that.
Upvotes: 1
Reputation: 16421
Yes, it's possible. Just use std::optional
(C++17 or from Boost) or unique_ptr
/shared_ptr
.
(opinion) optional
has a great advantage in the readability - you can't be any clearer that this value may not be initialized.
To show that resources are released correctly: first let's start with eager initialization (live):
ofstream file("test.txt");
file << "no flush\n";
ifstream inFile("test.txt");
string line;
getline(inFile, line);
cout << line << endl;
This, doesn't print anything for me¹. Let's move writing to a separate scope (live):
{
ofstream file("test.txt");
file << "no flush\n";
}
ifstream inFile("test.txt");
string line;
getline(inFile, line);
cout << line << endl;
This should print no flush
, because ofstream
is guaranteed to close()
the file upon destruction. (unless something else accessed test.txt
at the same time)
And now with Boost.Optional and lazy init (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;
Resources are released at the same time as they were with regular ofstream
.
¹ file access isn't guaranteed to be buffered, but it makes for a good example, also available on online compilers.
Upvotes: 3