2009-06-18 17 views
10

Sono sicuro che questa è una domanda molto semplice. Il seguente codice mostra quello che sto cercando di fare:Il modo giusto per inizializzare condizionalmente una variabile membro C++?

class MemberClass { 
public: 
    MemberClass(int abc){ } 
}; 

class MyClass { 
public: 
    MemberClass m_class; 
    MyClass(int xyz) { 
     if(xyz == 42) 
      m_class = MemberClass(12); 
     else 
      m_class = MemberClass(32); 
    } 
}; 

Questo non si compila, perché m_class è stato creato con un costruttore vuoto (che non esiste). Qual è il modo giusto per farlo? La mia ipotesi sta usando i puntatori e la creazione di istanze m_class usando new, ma spero che ci sia un modo più semplice.

Modifica: Avrei dovuto dirlo in precedenza, ma il mio problema attuale ha un'ulteriore complicazione: ho bisogno di chiamare un metodo prima di inizializzare m_class, al fine di configurare l'ambiente. Quindi:

class MyClass { 
public: 
    MemberClass m_class; 
    MyClass(int xyz) { 
     do_something(); // this must happen before m_class is created 
     if(xyz == 42) 
      m_class = MemberClass(12); 
     else 
      m_class = MemberClass(32); 
    } 
}; 

È possibile ottenere questo risultato con trucchi di elenco di inizializzazione di fantasia?

risposta

24

Utilizzare l'operatore condizionale. Se l'espressione è più grande, utilizzare una funzione

class MyClass { 
public: 
    MemberClass m_class; 
    MyClass(int xyz) : m_class(xyz == 42 ? 12 : 32) { 

    } 
}; 

class MyClass { 
    static int classInit(int n) { ... } 
public: 
    MemberClass m_class; 
    MyClass(int xyz) : m_class(classInit(xyz)) { 

    } 
}; 

Per chiamare una funzione prima di m_class inizializzazione, è possibile inserire una struct prima membro e sfruttare RAII

class MyClass { 
    static int classInit(int n) { ... } 
    struct EnvironmentInitializer { 
     EnvironmentInitializer() { 
      do_something(); 
     } 
    } env_initializer; 
public: 
    MemberClass m_class; 
    MyClass(int xyz) : m_class(classInit(xyz)) { 

    } 
}; 

Questo chiamerà do_something() prima di inizializzare m_class. Si noti che non è consentito chiamare le funzioni membro non statiche di MyClass prima che l'elenco di inizializzatore costruttore sia stato completato. La funzione dovrebbe essere un membro della sua classe base e la classe base 'ctor deve essere già completata affinché funzioni.

Si noti inoltre che la funzione, ovviamente, viene sempre chiamata, per ogni oggetto separato creato, non solo per il primo oggetto creato. Se si vuole fare questo, è possibile creare una variabile statica all'interno costruttore della inizializzatore:

class MyClass { 
    static int classInit(int n) { ... } 
    struct EnvironmentInitializer { 
     EnvironmentInitializer() { 
      static int only_once = (do_something(), 0); 
     } 
    } env_initializer; 
public: 
    MemberClass m_class; 
    MyClass(int xyz) : m_class(classInit(xyz)) { 

    } 
}; 

E 'utilizzando l'operatore virgola. Si noti che è possibile catturare qualsiasi eccezione lanciata da do_something utilizzando una funzione-tenta bloccare

class MyClass { 
    static int classInit(int n) { ... } 
    struct EnvironmentInitializer { 
     EnvironmentInitializer() { 
      static int only_once = (do_something(), 0); 
     } 
    } env_initializer; 
public: 
    MemberClass m_class; 
    MyClass(int xyz) try : m_class(classInit(xyz)) { 

    } catch(...) { /* handle exception */ } 
}; 

La funzione do_something si chiamerà la prossima volta, se ha gettato tale eccezione che ha causato l'oggetto MyClass non riescono a creare.Spero che questo aiuti :)

+0

Grazie! Ho aggiornato la mia domanda: in realtà ho bisogno di eseguire un metodo prima di creare m_class. È possibile? Forse potrei farlo in "classInit", ma non sarebbe molto elegante. –

+0

Imparato molto, grazie! :-) –

+0

Quando ho visto l'uso dell'operatore virgola, sapevo che la persona che rispondeva a questa domanda era intelligente. Quando ho visto l'uso della funzione try block, sapevo che la persona che rispondeva a questa domanda era litb. Quando ho visto che la persona che rispondeva a questa domanda era illuminata ... beh, immagino che sapessi già che era lui :) – mmocny

5

usare la sintassi lista di inizializzazione:

class MyClass { 
public: 
    MemberClass m_class; 
    MyClass(int xyz) : m_class(xyz == 42 ? MemberClass(12) : MemberClass(32) 
           /* see the comments, cleaner as xyz == 42 ? 12 : 32*/) 
    { } 
}; 

Probabilmente più pulito con una fabbrica:

MemberClass create_member(int x){ 
    if(xyz == 42) 
    return MemberClass(12); 
    // ... 
} 

//... 
MyClass(int xyz) : m_class(create_member(xyz)) 
+2

Si sta creando un oggetto senza nome e quindi si utilizza il copy-ctor per inizializzare il membro, anziché inizializzarlo direttamente. –

+0

Sì, digitando troppo velocemente per il mio bene. Personalmente, sposterei ancora la logica in MemberClass o in una fabbrica. –

5
MyClass(int xyz) : m_class(xyz==42 ? 12 : 32) {} 

Per rispondere alla tua domanda riveduta, che vengono un po 'complicato. Il modo più semplice sarebbe quello di rendere un puntatore m_class. Se lo vuoi davvero come membro dei dati, devi essere creativo. Crea una nuova classe (è meglio se è definita interna a MyClass). È necessario che sia la funzione che deve essere chiamata. Includilo prima tra le dichiarazioni dei membri dei dati (questo lo renderà il primo istanzionato).

class MyClass 
{ 
    class initer { public: initer() { 
        // this must happen before m_class is created 
        do_something();       
        } 
        } 

    initer  dummy; 
public: 

    MemberClass m_class; 
    MyClass(int xyz) : m_class(xyz==42? 12 : 43) 
    { 
     // dummy silently default ctor'ed before m_class. 
    } 
}; 
0

Oppure:

class MemberClass { 
public: 
    MemberClass(int abc){ } 
}; 

class MyClass { 
public: 
    MemberClass* m_class; 
    MyClass(int xyz) { 
     if(xyz == 42) 
      m_class = new MemberClass(12); 
     else 
      m_class = new MemberClass(32); 
    } 
}; 

Se in qualche modo ancora desidera mantenere la stessa sintassi. L'initalizzazione dei membri è tuttavia più efficiente.

0

Prova questo:

class MemberClass 
{ 
public:  
    MemberClass(int abc = 0){ } 
}; 

Questo dà un valore di default, e il vostro costruttore di default.

+1

A volte ci sono dei motivi per non avere un costruttore predefinito. –

+0

Dovresti avere un parametro predefinito che lo inserisce in uno stato zombi noto. Non ideale ma a volte l'unico modo. –

+0

A volte ci sono dei motivi per rendere privato il costruttore predefinito, ma dovrebbe essere sempre lì ... – Kieveli

0

Per avere l'inizializzazione accade dopo altre cose accade, si ha davvero bisogno di utilizzare i puntatori, qualcosa di simile:

class MyClass { 
public: 
    MemberClass * m_pClass; 
    MyClass(int xyz) { 
     do_something(); // this must happen before m_class is created 
     if(xyz == 42) 
      m_pClass = new MemberClass(12); 
     else 
      m_pClass = new MemberClass(32); 
    } 
}; 

L'unica differenza è che avrete bisogno di accedere alle variabili membro come m_pClass->counter invece di m_class.counter e delete m_pClass nel distruttore.

+0

L'alternativa è inizializzare Myclass in uno stato fittizio di zombie e riavviare successivamente una volta che si conosce il valore. Ma preferirei dei suggerimenti. –

Problemi correlati