2015-05-06 14 views
7

Sono sicuro che la seguente domanda ha già una buona risposta da qualche altra parte, ma è difficile da trovare poiché non conosco il " nome "del mio problema.Come progettare una classe che è costante dopo l'inizializzazione ed esiste solo una volta nell'intero programma

Sto progettando una classe/oggetto/"qualcosa" con le seguenti proprietà:

  • Si tratta di una sorta di tabella di ricerca.
  • Non cambia dopo l'inizializzazione.
  • Ha diversi membri non primitivi.
  • Ha una funzione di inizializzazione complicata.
  • È lo stesso per l'intero programma.
  • Viene parametrizzato dai parametri del modello.

Quindi questo suona come una classe template statico:

template <int T> 
class LookupTable{ 

    public: 
    static void init(){ 
     // create entries depending on T 
    } 

    private: 
    static vector<Entries> entries; 

} 

Quello che mi piace è che ho bisogno di chiamare init() da qualche parte nel mio programma. Quindi la prima domanda è: Come posso rendere questa classe completamente autonoma, senza bisogno di essere inizializzata esplicitamente da qualche parte?

Seconda parte: Qual è l'approccio di progettazione generale per implementare tale classe? Sarei perfettamente felice con un link per un buon esempio.

Un possibile candidato è il singleton. Ma ho qualche dubbio: - Il singleton considerava un cattivo design in molti casi. Va bene per una tabella di ricerca come descritto? - Singleton è una notazione piuttosto lunga, poiché devo usare LookupTable::getInstance()->getEntry(idx).

+0

per la prima parte, utilizzare il costruttore –

+3

Quello che si desidera è chiamato 'Singleton'. Se cerchi google, puoi trovare molte soluzioni di esempio per C++ – maja

+0

possibile duplicato di [modello di progettazione Singleton C++] (http://stackoverflow.com/questions/1008019/c-singleton-design-pattern) – maja

risposta

5

Singleton è lo schema, ma utilizzare la variante più sicura dove questo approccio evita le condizioni di fiasco e thread race di inizializzazione statica e dal momento che ci si è lamentati della lunghezza - possiamo accorciare ulteriormente passando gli indici attraverso la funzione get_entry:

template <int T> 
class LookupTable{ 
    public: 
    static std::vector<Entry> make_entries(){ ...} 
    static const std::vector<Entry>& get_entries(){ 
     static const std::vector<Entry> instances = make_entries(); 
     return instances; 
    } 
    static const Entry& get_entry(size_t idx){ 
     return get_entries()[idx]; 
    } 
}; 

Un altro approccio che evita tutti i mali di un singleton non è quello di usare un singleton: basta passare una normale vecchia classe direttamente come solo un altro parametro. Lo faccio con molte implementazioni di funzioni crc con tabelle relativamente pesanti ... la maggior parte delle cose non mi interessa e quindi non devi fare i conti con modelli di design. Ed è più veloce.

+0

Molte buone risposte, ma penso che questo abbia il miglior mix tra semplicità ed elaboratezza, quindi accetto questa risposta. – Michael

0

Si dovrebbe essere in grado di realizzare ciò che si desidera semplicemente avendo un'istanza static const; devi solo dare alla classe un costruttore predefinito (che sarebbe equivalente alla tua funzione init()). Se hai bisogno di diversi costruttori basati sul tipo T, allora puoi specializzare lo LookupTable<T> per questi tipi.

Detto questo, c'è una trappola di cui dovresti essere a conoscenza: the static initialization order fiasco. Se si dispone di altri oggetti static che si riferiscono all'istanza LookupTable<T>, è possibile che si verifichino problemi poiché l'ordine in cui sono inizializzati non è specificato.

+0

In questo modo, tutto nel file di intestazione: 'modello classe LookupTable {...}; static const LookupTable <20> lut; '? – Michael

2

Se si desidera rendere una classe completa statica che non si ottiene un'istanza e viene impostata solo una volta, si dovrebbe essere in grado di utilizzare tutte le funzioni statiche e avere una funzione Init() che non restituisce nulla e determina se Init() è già stato chiamato. Questo è solo un ritocco al design Singleton.

In modo da non dover chiamare Init() da qualche parte nel codice, è possibile chiamare Init() come prima riga di ogni funzione della classe. Dal momento che Init() non farà nulla se è già stato chiamato non cambierà nulla. Puoi anche rendere privato lo Init() se lo desideri.

class StaticClass 
{ 
public: 
    static void Init() 
    { 
     static bool created = false 
     if(!created) 
     { 
      // here we setup the class 
      created = true; // set to true so next time someone class Init() it is a do nothing operation. 
     } 
    } 
    //... 

private: 
    StaticClass() {} 
    //... 
}; 

Poiché non v'è alcun modo per ottenere un'istanza di un StaticClass dato che la funzione Init() è nulla davvero non hanno bisogno di preoccuparsi per il costruttore di copia o l'operatore di assegnazione.

2

Meyer's Singleton in soccorso!

template <class T> 
struct LookupTable { 

    static LookupTable &get() { 
     static LookupTable lut; 
     return lut; 
    } 

private: 
    LookupTable() { 
     // Your initialization 
    } 

    LookupTable(LookupTable const &) = delete; 
    LookupTable operator = (LookupTable const &) = delete; 
}; 

Usage:

LookupTable<int>::get() // Will initialize on first call. 

Si può sovraccaricare gli operatori per semplificare l'indicizzazione, o anche nasconderlo in get().

4

Sto progettando una classe/oggetto/"qualcosa" con le seguenti proprietà:

• Si tratta di una sorta di tabella di ricerca.

class LookupTable 
{ 
}; 

• Non cambia dopo l'inizializzazione.

codice client:

const LookupTable lookup_table = ...; 
^^^^^ 

• Ha diversi membri non primitive.

class LookupTable 
{ 
    std::vector<Entry> entries; 
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
}; 

• Ha una funzione di inizializzazione complicata.

class LookupTable 
{ 
    std::vector<Entry> entries; 
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
public: 
    explicit LookupTable(
     std::vector<Entry> e 
     // if more members are required, receive them here, 
     // fully constructed 
    ): entries{ std::move(e) } {} 
}; 

LookupTable make_lookup_table() 
{ 
    std::vector<Entry> entries; 

    // perform complicated value initialization here 
    // and once everything is initialized, pass to new instance of 
    // LookupTable which is returned 

    return LookupTable{ std::move(entries) }; 
} 

codice client:

const auto lookup_table = make_lookup_table(); 

• E 'lo stesso per l'intero programma.

Utilizzare l'iniezione di dipendenza nel codice che lo utilizza.

• Viene parametrizzato dai parametri del modello.

È sufficiente aggiungere i parametri del modello al codice sopra, in base alle proprie esigenze.

Cose da notare:

  • non c'è nulla nel codice di suggerire una singola istanza esisterà. Questo fa parte dell'uso della classe (codice client), non è definizione.

  • questo non è un Singleton. Singleton è (da molti punti di vista) e antipattern.

  • probabilmente sarà necessario definire più istanze della classe in futuro (probabilmente per il test dell'unità); non c'è niente qui che ti impedisca di farlo.

  • la parte di inizializzazione complessa è centralizzata (e nascosta) in una funzione di fabbrica. Se l'inizializzazione fallisce, non viene creata alcuna istanza. Se l'inizializzazione cambia, l'interfaccia pubblica della classe non cambierà. Se è necessario aggiungere diverse inizializzazioni in diversi casi (debug contro release, test e produzione, configurazioni runtime veloci e sicure) non è necessario eliminare o modificare il codice esistente, basta aggiungere una nuova funzione di fabbrica.

1

Se è possibile compilare con C++ 14, hai considerato di utilizzare un modello variabile?

// Complicated initializer function that create entries depending on T 
// could be specialized for T. 
template <int T> 
constexpr std::vector<Entries> init() { return {T, Entries{}}; } 

// Class with several non-primitive members. 
template <int T> 
class LUT { 
public: 
    constexpr LUT() : entries{init<T>()} {} 
    auto foo() const { return entries.size(); } 
    const void *bar() const { return entries.data(); } 
    const void *baz() const { return this; } 

private: 
    std::vector<Entries> entries; 
}; 

// Variable template parametrized by template parameters. 
// It will be the same for the whole program. 
template <int T> 
LUT<T> LookupTable{}; 

void f15() { std::cout << LookupTable<15>.foo() << '\n'; } 
void f5() { std::cout << LookupTable<5>.foo() << '\n'; } 

int main() 
{ 
    // LookupTable<15> is the same here and in f15 
    std::cout << LookupTable<15>.foo() << ' ' 
       << LookupTable<15>.bar() << ' ' 
       << LookupTable<15>.baz() << '\n'; 

    // LookupTable<5> is the same here and in f5 
    std::cout << LookupTable<5>.foo() << ' ' 
       << LookupTable<5>.bar() << ' ' 
       << LookupTable<5>.baz() << '\n'; 
    return 0; 
} 

Ha raggiunto i tuoi requisiti?

  • Si tratta di una sorta di tabella di ricerca: Non lo so, è fino al LUT attuazione.
  • Non cambia dopo l'inizializzazione: Una volta LookupTable viene inizializzato (prima di chiamare main) non poteva essere cambiato *, assicurarsi di contrassegnare tutti i LUT funzioni const pure.
  • Ha diversi membri non primitivi: non so, è fino all'implementazione LUT.
  • Ha una funzione di inizializzazione complicata: Rendi la funzione init() complicata come vuoi ma considerando che verrà richiamata durante l'inizializzazione statica.
  • È lo stesso per l'intero programma: Ogni istanza LookupTable<NUMBER> è la stessa per l'intero programma per ogni NUMBER fornito.
  • Viene parametrizzato dai parametri del modello: AFAIK lo è.

Speranza che aiuta demo


* Non so il motivo per cui template <int T>constLUT<T> LookupTable{}; fallisce, ma in ogni caso LUT mancanze di operator =.

Problemi correlati