Defines a uniform class interface to initialize/uninitialize non-trivial resources.
More...
Defines a uniform class interface to initialize/uninitialize non-trivial resources.
- Constructor/Destructor vs initialize()/uninitialize()
- Constructor should not do complicated initialization as it can't return errors. Instead, we universally use initialize()/uninitialize() semantics for all long-living objects. By long-living, we mean at least seconds. Small and short-living objects do not have to follow this semantics as it might cause unnecessary complexity or overhead.
Same applies to Destructor vs uninitialize(). Furthermore, C++ doesn't allow calling virtual functions (uninitialize()) in destructor as the class information was already torn down! So, make sure you always explicitly call uninitialize(). If you didn't call uninitialize(), you actually leak the resource.
- DefaultInitializable
- For most classes, you can use DefaultInitializable class to save repetitive code. For example, declare your class like the following:
class YourClass : public DefaultInitializable {
public:
YourClass(const ConfigurationForYourClass &conf, int other_params, ...);
...
};
Then, define initialize_once()/uninitialize_once() as follows in cpp. ErrorStack YourClass::initialize_once() {
....
}
ErrorStack YourClass::uninitialize_once() {
ErrorStackBatch batch;
batch.emplace_back(some_uninitialization());
batch.emplace_back(another_uninitialization());
batch.emplace_back(yet_another_uninitialization());
}
- UninitializeGuard, or how RAII fails in real world.
- The code that instantiates an Initializable object must call uninitialize() for the reasons above. Even if there is some error, which causes an early return from the function by CHECK_ERROR() in many cases. Note that try-catch(...) does not work because this is NOT an exception (and we never use exceptions in our code). YOU have to make sure uninitialize() is called. UninitializeGuard addresses this issue, but imperfectly. For example, use it like this:
ErrorStack your_func() {
YourInitializable object;
{
}
}
The UninitializeGuard object automatically calls object.uninitialize() when do_something() fails and we return from your_func(). HOWEVER, C++'s destructor can't propagate any errors, in our case ErrorStack. The second argument (UninitializeGuard::kWarnIfUninitializeError) says that if it encounters an error while uninitialize() call, it just warns it in debug log. Logging the error is not a perfect solution at all. So, this is just an imperfect safety net. Unfortunately, this is all what we can do in C++ for objects that have non-trivial release.