Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I use the Initialize-on-demand idiom, and if so how?

Tags:

c++

idioms

I have the following code:

 MyType x = do_something_dangerous();
 // ...
 if (some_condition) {
     // ...
     bar(x);
 }
 else { 
     // ...
 }
 // ...
 if (another_condition_which_may_depend_on_previous_if_else} {
     // ...
     baz(x);
 }

The idea is that in some cases, which are perhaps difficult/inconvenient to determine in advance, I need to use x. But in the cases I don't need to use it, trying to initialize it may be bad (say, could crash my process).

Now, it seems like what I need to be using is an initialize-on-demand holder (the link focuses on Java, so here's a sketch): Some kind of Wrapper<MyType> or Wrapper<MyType, do_something_dangerous> with a get() method, such that the first get() calls do_something_dangerous() and later get()s just pass the value the first call obtained.

  • Is this indeed an appropriate approach here?
  • Is there some standard(ish) implementation of this idiom, or a variant of it?

Notes:

  • I could use boost::optional, but that would be a bit cumbersome and also twist the intended use: "It is recommended to use optional<T> in situations where there is exactly one, clear (to all parties) reason for having no value of type T, and where the lack of value is as natural as having any regular value of T."
like image 868
einpoklum Avatar asked Apr 22 '15 07:04

einpoklum


Video Answer


2 Answers

IMHO, the solution you propose is perfectly appropriate :

  • it is trivial to implement
  • it fully meet your needs

Of course, if you are already using boost in your application you can look at the suggested boost::optional module, but I'm not sure it is exactly what you want as it is more designed for nullable objects when what you need is defered initialization.

My advice here is : stick to a dedicated wrapper implementation.

Unlike other answers, I think you should not use a singleton but simply something like (implementation using an initialization parameter) :

template<typename T, typename I>
class LazyInit {
    T* val;
    I init;

public:
    LazyInit<T, I>(I init): init(init) {
        val = NULL;
    }
    ~LazyInit<T, I>() {
        delete val;
        val = NULL; // normally useless here
    }
    T& get() {
        if (val == NULL) {
            val = new T(init);
        }
        return *val;
    }
};

And here is a implementation using an initialization function :

template<typename T>
class LazyInit {
    T* val;
    T (*init)();

public:
    LazyInit<T>(T (*init)()): init(init) {
        val = NULL;
    }
    ~LazyInit<T>() {
        delete val;
        val = NULL; // normally useless here
    }
    T& get() {
        if (val == NULL) {
            val = new T(init());
        }
        return *val;
    }
};
...
LazyInit<MyType> x(do_something);
...
bar(x.get()); // initialization done only here

You could easily combine both to build an implementation using a function taking a parameter.

like image 111
Serge Ballesta Avatar answered Oct 23 '22 11:10

Serge Ballesta


If you have c++11 you can use a combination of std::async and std::shared_future:

#include <iostream>
#include <future>

using namespace std;

struct LazyObj {
    LazyObj() {
        cout << "LazyObj init" << endl;
    }
    void test() {
        cout << "LazyObj test" << endl;
    }
};

int main() {
    auto x = std::async(launch::deferred, []() -> LazyObj& {
        static LazyObj a;
        return a;
    }).share();

    int cond=0;
    if( cond == 0 ) {
        x.get().test();
        x.get().test();
    }
    return 0;
}

In this use-case launch::deferred points std::async not to create execution thread and call the lambda on-demand when get() is called. To allow multiple calls of get() method, we convert std::future to std::shared_future using share() method. Now we able to get lazy-initialisaion object when or where it needed.

like image 26
PSIAlt Avatar answered Oct 23 '22 11:10

PSIAlt