2015-04-22 32 views
9

Ho il codice seguente:Devo usare l'idioma Initialize-on-demand e, in caso affermativo, come?

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

L'idea è che in alcuni casi, che sono forse difficile/scomodo da determinare in anticipo, ho bisogno di usare x. Ma nei casi in cui non ho bisogno di usarlo, provare a inizializzarlo potrebbe essere brutto (per esempio, potrebbe bloccare il mio processo).

Ora, sembra che quello che ho bisogno di utilizzare è un initialize-on-demand holder (il link si concentra su Java, quindi ecco un abbozzo): Una specie di Wrapper<MyType> o Wrapper<MyType, do_something_dangerous> con un metodo get(), in modo tale che i primi get() chiamate do_something_dangerous() e successivamente get() s basta passare il valore ottenuto dalla prima chiamata.

  • È davvero un approccio appropriato qui?
  • Esiste un'implementazione standard (ish) di questo idioma o una variante di esso?

Note:

  • potevo usare boost::optional, ma sarebbe un po 'ingombrante e anche torcere la destinazione d'uso: "Si raccomanda di utilizzare optional<T> in situazioni in cui non v'è esattamente una, chiaro (a tutte le parti) motivo per non avere valore di tipo T, e dove la mancanza di valore è naturale come avere qualsiasi valore regolare di T. "
+1

Forse 'boost :: optional' o' std :: experimental :: optional'? –

+0

Penso che il termine che stai cercando sia la memoizzazione, ecco un'altra domanda che potrebbe aiutarti: http://stackoverflow.com/questions/17805969/writing-universal-memoization-function-in-c11 – harald

+0

@NickyC: vedi nota. – einpoklum

risposta

5

IMHO, la soluzione che proponete è perfettamente appropriato:

  • è banale da implementare
  • che soddisfa pienamente le vostre esigenze

Naturalmente, se si sta già utilizzando spinta nella tua applicazione puoi dare un'occhiata al modulo boost :: module opzionale suggerito, ma non sono sicuro che sia esattamente quello che vuoi dato che è più progettato per oggetti nullable quando ciò di cui hai bisogno è l'inizializzazione posticipata.

Il mio consiglio qui è: attenersi a un'implementazione del wrapper dedicato.

A differenza di altre risposte, penso che si dovrebbe non utilizzare un Singleton, ma semplicemente qualcosa di simile (implementazione utilizzando un parametro di inizializzazione):

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; 
    } 
}; 

E qui è un'implementazione utilizza una funzione di inizializzazione:

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 

Si potrebbe facilmente combinare entrambi per costruire un'implementazione utilizzando una funzione che accetta un parametro.

+0

Vedi modifica per quanto riguarda boost :: opzionale, è proprio come hai detto tu. Inoltre, non c'è nulla di simile nella libreria standard o in Boost? Infine, supponi che io stia usando un ctor predefinito; la classe di @ TartanLama non funzionerebbe meglio? – einpoklum

+0

Questo approccio sarebbe probabilmente meglio per voi perché la soluzione statico-funzionale manterrà una singola istanza per tutte le istanze di una singola istanza di modello. – TartanLlama

+0

@einpoklum: qui ci sono implementazioni che usano costruttori non predefiniti ... –

2

Se ha C++ 11 è possibile utilizzare una combinazione di std::async e 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 questo caso d'uso launch::deferred punti std::async non creare thread di esecuzione e chiamare il lambda su richiesta quando get() è chiamato. Per consentire più chiamate del metodo get(), convertiamo std::future in std::shared_future utilizzando il metodo share(). Ora siamo in grado di ottenere l'oggetto pigro-initialisaion quando o dove necessario.

+0

Nel tuo esempio, la parte "pericolosa" sta costruendo 'LazyObj a'. Ora, cosa mi assicura che succederà quando me lo aspetterò? cioè, quando il lambda viene invocato per la prima volta? – einpoklum

+0

@einpoklum yes, quando è stato invocato per la prima volta. Puoi testarlo impostando 'cond = 1' – PSIAlt