0

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?

athos
  • 6,120
  • 5
  • 51
  • 95
  • Yes, of course it's possible. As for industry practice I'm not aware of any existing applications for such an object. – Mark Ransom Oct 02 '16 at 03:26
  • @MarkRansom why no such implementation? is it such concept is not reasonable? – athos Oct 02 '16 at 03:27
  • There's no need to implement the concepts together. Just use a `shared_ptr` or `unique_ptr` for your lazy object. – Mark Ransom Oct 02 '16 at 03:30
  • @MarkRansom oh! is it something like this -- http://stackoverflow.com/questions/8362513/shared-weak-and-lazy-pointers-in-c ? just want to find some sample code to read. – athos Oct 02 '16 at 03:34
  • That would be "RACAI", wouldn't it? "Resource acquisition comes after initialization". – Kerrek SB Oct 02 '16 at 03:43
  • @KerrekSB or RAII@II "Resource Aqcuisition Is Initialization At Inner Implmentation" – athos Oct 02 '16 at 03:45
  • The more I think about it, the more I don't get this question. It's all in the name already: "resource acquisition *is initialization*". If you don't want to acquire the resource, then don't initialize the handler object! The motivation of this question seems to miss the point of RAII entirely. – Kerrek SB Oct 02 '16 at 12:53

2 Answers2

3

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.

krzaq
  • 16,240
  • 4
  • 46
  • 61
  • I'm not sure if this is exactly what you asked for, but I can't think of any other resources available on online compilers that can be used to prove this... – krzaq Oct 02 '16 at 03:57
  • This is not really lazy-initialized, IMO. Rather, you have uninitialized, followed by assigned. Rather I think of lazy init as being init just prior to get, usage, etc. But that the class itself is handling that for you by some user provided factory callback. I think in terms of dotnet Lazy, for instance. – mwpowellhtx Nov 13 '18 at 17:38
1

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.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • So in generatl it's the constructor to initialize so that member functions can use the resource assuming it is in valide status, until destructor to clean it up, hence it's not recommended to do lazy initialization? But there are lazy initialization everywhere. I'm confused, this can't be a C++ special practice. Do you mean the mob is wrong, just like people still use Singleton (http://www.onelib.org/article/singletons-solving-problems-you-didnt-know-you-never-had-since-1995)? – athos Oct 02 '16 at 04:22
  • Sure, there are cases where lazy initialisation is used. While there are some circumstances where doing that makes sense, a lot of them are simply a hangover from C, where (in practice) it is often necessary that creating an object and initialising it are distinct operations. That separation is rarely necessary in C++, because the act of creating an object with a constructor initialises it in one step (albeit with details of making that happen managed by the compiler). And you asked about mixing RAII with lazy initialisation - which is one case where lazy initialisation is a poor idea. – Peter Oct 02 '16 at 04:38