9

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."
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    Perhaps `boost::optional` or `std::experimental::optional`? –  Apr 22 '15 at 07:43
  • I think the term you're looking for is memoization, here's another question that may help: http://stackoverflow.com/questions/17805969/writing-universal-memoization-function-in-c11 – harald Apr 22 '15 at 07:47
  • it's not very clear to me the desired workflow: is what is dangerous: the call to `do_something_dangerous()`, the initialization of `x` if that function failed, the use of `x` if that function failed. Can x be default initialized (or something equivalent) in such way that it does no harm and it is detectable? – bolov Apr 22 '15 at 08:22

2 Answers2

5

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.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • See edit regarding boost::optional, it's just like you said. Also, is there nothing like this in the standard library or in Boost? Finally, you're assuming I'm using a default ctor; wouldn't @TartanLama's class work better? – einpoklum Apr 22 '15 at 08:22
  • This approach would probably be better for you because the function-static solution will maintain a single instance for all instances of a single template instance. – TartanLlama Apr 22 '15 at 08:37
  • What I would do here is instead of `T*` I'd use a `boost::optional` as member because that way you (a) avoid the dynamic memory allocation and (b) the d'tor comes for free. – Martin Ba Apr 22 '15 at 09:20
  • I probably would also templateize the version taking an init function so that any callable could be passed, not just a raw function pointer. – Martin Ba Apr 22 '15 at 09:21
  • @MartinBa : you are right for the templateization, by I must admit I've reached my limits regarding the usage of function types in template ... Feel free to propose an edit here or add your own answer. Concerning boost, as I mainly use C and C++ for low level code or heavily used routines requiring optimisation, I seldom use it. – Serge Ballesta Apr 22 '15 at 09:35
2

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.

Galimov Albert
  • 7,269
  • 1
  • 24
  • 50
  • In your example, the 'dangerous' part is constructing `LazyObj a`. Now, what assures me this will happen when I expect it to? i.e. when the lambda is first invoked? – einpoklum Apr 22 '15 at 14:14
  • @einpoklum yes, when it first invoked. You can test it by setting `cond=1` – Galimov Albert Apr 22 '15 at 14:23