2011-08-26 11 views
6

Sono nuovo del C++ e l'intera idea delle classi - Sto ancora leggendo un libro per provare e imparare. Il libro che sto leggendo dice che quando costruisco una classe, posso assegnare valori di default in questo modo:Costruzione classe con valori iniziali

class foo { 
public: 
    foo(char c, int i); 
private: 
    char exampleChar; 
    int exampleInt; 
}; 

foo::foo(char c, int i): 
exampleChar(c), 
exampleInt(i) 
{} 

Questo codice (per me) sembra molto disordinato, e non seguire le regole che io sono usato in altre lingue. La mia domanda è, qual è la differenza tra fare quanto sopra, e questo (sotto, che personalmente penso sembra molto più pulito)?

foo::foo(char c, int i) { 
    exampleChar = c; 
    exampleInt = i; 
} 

genere di cose che sto pensando sono: ci sono problemi di prestazioni/efficienza se fatto su larga scala - o è esattamente la stessa cosa?

+1

"Questo codice ... non segue le regole a cui sono abituato in altre lingue" Bene, il C++ non è una di quelle altre lingue. Ci sono molte cose in C++ che sono differenti rispetto a "altre lingue" (e, probabilmente, ci sono molte cose in ognuna delle altre lingue che sono diverse dalle altre di quelle altre lingue). –

+1

domande molto simili: http://stackoverflow.com/questions/1598967/benefits-of-initialization-lists, http://stackoverflow.com/questions/926752/why-should-i-prefer-to-use-member -initialization-list, http://stackoverflow.com/questions/4589237/c-initialization-lists, e probabilmente molto altro ancora –

risposta

10

Il primo modo, facendo : exampleChar(c), exampleInt(i) è chiamato un elenco di inizializzazione.

Se lo fate il secondo modo, le due variabili sono predefinita costruito primo , allora si assegna loro un valore. (Quando viene inserito il corpo reale del costruttore, tutto ciò che non è stato inizializzato dall'elenco di inizializzazione è costruito di default). Questo è uno spreco di tempo perché si stanno comunque sovrascrivendo i valori. Per piccoli tipi come int o char questo non è un grosso problema, ma quando quelle variabili membro sono di grandi dimensioni che richiederebbero molti cicli di costruzione, si consiglia di utilizzare l'elenco di inizializzazione.

Il secondo modo non perderà tempo assegnandogli un valore predefinito e quindi sovrascrivendolo: imposterà i propri valori direttamente sul valore che gli viene assegnato (o chiama il costruttore corretto se il membro è un oggetto).

Si può vedere che cosa si intende in questo modo:

class MyClass { 
public: 
    int _i; // our data 

    // default constructor 
    MyClass() : _i(0) { cout << "default constructor"; } 

    // constructor that takes an int 
    MyClass(int i) : _i(i) { cout << "int constructor"; } 

    // assignment operator 
    void operator=(int i) { _i = i; cout << "assignment operator"; } 
}; 

class OtherClass { 
public: 
    MyClass c; 

    OtherClass() { 
     c = 54; 
    } 
}; 

OtherClass oc; 

vedrai che

default constructor 
assignment operator 

viene stampato. Sono due chiamate di funzione che, per altre classi, potrebbero essere costose.

Se si modifica il costruttore di OtherClass per

OtherClass() : c(54) { } 

Vedrai che

int constructor 

viene stampato. Solo una chiamata rispetto a due. Questo è il modo più efficiente.

liste di inizializzazione sono anche un must quando si

  1. hanno tipi che non hanno alcun costruttore di default. Devi chiamare il costruttore giusto nella lista di inizializzazione.

  2. hanno un membro const che si vuole dare un certo valore (e non solo hanno permantently il valore di default

  3. hanno un membro di riferimento. È necessario utilizzare gli elenchi di inizializzazione su questi.

tl; dr: fallo perché è altrettanto veloce ma mai più lento dell'altro modo, e talvolta molto più veloce.

Per tipi integrati come int e char, in realtà non sono affatto costruiti; hanno solo il valore di qualsiasi memoria abbiano mai avuto in precedenza.

+0

C++ non assegna valori di default alle primitive. Ecco perché l'uso di variabili non assegnate ti dà un avvertimento: è un comportamento indefinito perché contiene un valore casuale che si trova in quel blocco di memoria. –

+0

@Yochai buona cattura, ho dimenticato di dirlo. Aggiunta una nota –

1

Beh, questa è una domanda tipica FAQ: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.6

Nel tuo caso, utilizzando char e int non ci sono differenze.

Regola generale: utilizzare la lista di inizializzazione come possibile, ci sono pochissimi casi in cui si può preferire l'assegnazione, ad esempio per migliorare la leggibilità:

MyClass::MyClass 
{ 
    a = b = c = d = e = f = 0; 
} 

è meglio di

class MyClass::MyClass : a(0), b(0), c(0), d(0), e(0), f(0) { } 
5

La differenza è che il compilatore inizializzerà sempre tutti i membri (in ordine di dichiarazione) prima della prima istruzione del costruttore definita dall'utente. Nel caso di uno char e uno int, che sono entrambi tipi primitivi, "inizializzazione" in realtà significa "nessuna inizializzazione" qui. Tuttavia, se si dispone di un membro con un costruttore che fa un po 'di lavoro effettivo, questo costruttore sarà chiamato in anticipo - se si fa

foo::foo() { 
    myComplexMember = MyComplexClass(42); 
} 

il compilatore ha già invocare il costruttore di default MyComplexClass prima il codice stato chiamato, che è uno spreco di risorse (e un errore del compilatore se il ctor predefinito non è accessibile).

Utilizzando un elenco di inizializzazione, è possibile personalizzare l'inizializzazione predefinita ed evitare di fare le cose per niente. Ovviamente, questa è la strada da percorrere.

5

Ci sono cose che puoi fare in questo modo che non potresti altrimenti.

  1. Se uno dei membri non ha un costruttore predefinito. Questo è l'unico modo per iniziare il membro alla costruzione. (lo stesso vale per la classe base)

  2. È possibile assegnare un valore a un membro const.

  3. È possibile assicurare uno stato definito per la classe prima che la funzione del costruttore inizi a funzionare.

  4. Se un membro è un riferimento, deve essere inizializzato nell'elenco di inizializzazione. Perché i riferimenti sono immutabili e possono essere inizializzati solo una volta all'inizio (come const)

1

Se i membri avevano costruttori non banali, nel seguente codice primi costruttori di default sarebbe chiamato, quindi le assegnazioni sarebbero eseguito, mentre nel codice sopra sarebbero inizializzati solo una volta. Quindi sì, potrebbe esserci un problema di prestazioni.

C'è anche un problema pratico: se sono const, riferimenti o non hanno costruttori predefiniti, non è possibile utilizzare la versione di seguito.

1

C'è una sottile ma importante differenza tra queste due opzioni. Quello che hai in cima è chiamato un elenco di inizializzazione dei membri. Quando l'oggetto viene creato, i membri in questo elenco vengono inizializzati in base a ciò che inserisci tra parentesi.

Quando si esegue assegnazione nel corpo del costruttore, i valori vengono prima inizializzato, e quindi assegnato. Pubblicherò un breve esempio qui sotto.

Esempio 1: inizializzazione Stati

class foo 
{ 
public: 
    foo(char c, int i); 

private: 
    char exampleChar; 
    int exampleInt; 
    Bar exampleBar; 
}; 

foo::foo(char c, int i): 
    exampleChar(c), 
    exampleInt(i), 
    exampleBar()  //Here, a bar is being default constructed 
{ 
} 

Esempio 2: Constructor Assegnazione

class foo 
{ 
public: 
    foo(char c, int i, Bar b); 

private: 
    char exampleChar; 
    int exampleInt; 
    Bar exampleBar; 
}; 

foo::foo(char c, int i, Bar b): 
    //exampleChar(c), 
    //exampleInt(i), 
    //exampleBar() 
{ 
    exampleChar = c; 
    exampleInt = i; 
    exampleBar = someOtherBar; //Here, a bar is being assigned 
} 

Questo è dove si fa interessante. Notare che exampleBar si assegna. Dietro le quinte, un Bar viene effettivamente creato per primo, anche se non lo hai specificato. Inoltre, se il tuo Bar è più complicato di una semplice struttura, dovrai essere sicuro di implementare l'operatore di assegnazione in modo che tu possa inizializzarlo in questo modo. Inoltre, per inizializzare lo Bar da un altro Bar dall'elenco di inizializzazione dei membri, è necessario implementare il costruttore di copie!

Esempio 3: Copia costruttore utilizzati negli Stati init

class foo 
{ 
public: 
    foo(char c, int i, Bar b); 

private: 
    char exampleChar; 
    int exampleInt; 
    Bar exampleBar; 
}; 

foo::foo(char c, int i, Bar b): 
    //exampleChar(c), 
    //exampleInt(i), 
    exampleBar(b)  //Here, a bar is being constructed using the copy constructor of Bar 
{ 
    exampleChar = c; 
    exampleInt = i; 
} 
0

vorrei prendere l'abitudine di usare gli elenchi di inizializzazione. Non soffriranno di problemi quando qualcuno cambia un char in qualche oggetto dove viene chiamato per primo il costruttore predefinito, e anche per la correttezza const per i valori const!

0
foo::foo(char c, int i):exampleChar(c),exampleInt(i){} 

Questo costrutto è chiamato Lista Utenti Initializer in C++.

E inizializza vostro membro exampleChar ad un valore c & exampleInt-i.


Qual è la differenza tra l'inizializzazione e assegnazione all'interno costruttore? &
Qual è il vantaggio?

C'è una differenza tra Inizializzare un membro usando l'elenco di inizializzatore e assegnargli un valore all'interno del corpo del costruttore.

Quando si inizializzano i campi tramite l'elenco di inizializzazione, i costruttori verranno chiamati una volta.

Se si utilizza l'assegnazione, i campi verranno inizialmente inizializzati con i costruttori predefiniti e quindi riassegnati (tramite l'operatore di assegnazione) con i valori effettivi.

Come si vede c'è un ulteriore sovraccarico di creazione & assegnazione in quest'ultimo, che potrebbe essere considerevole per le classi definite dall'utente.

Per un tipo di dati intero (per il quale lo si utilizza) o per i membri della classe POD non è previsto un sovraccarico pratico.

Problemi correlati