A cosa serve l'elenco delle variabili membro dopo i due punti in un costruttore?

StackOverflow https://stackoverflow.com/questions/210616

  •  03-07-2019
  •  | 
  •  

Domanda

Sto leggendo questo codice open source C ++ e sono arrivato a un costruttore ma non lo capisco (sostanzialmente perché non conosco C ++: P)

Capisco molto bene C e Java.

 TransparentObject::TransparentObject( int w, int x, int y, int z ) : 
     _someMethod( 0 ),
     _someOtherMethod( 0 ),
     _someOtherOtherMethod( 0 ),
     _someMethodX( 0 ) 
  {
       int bla;
       int bla;
  }

Per quanto posso " dedurre " La prima riga dichiara solo il nome del costruttore, il " :: " suona come " appartiene a " per me. E il codice tra {} è il corpo del costruttore stesso.

I " think " cosa succede dopo i paremeters e il primo " {" sono come i parametri di default dei metodi o qualcosa del genere, ma non trovo una spiegazione ragionevole sul web. La maggior parte dei costruttori C ++ che ho trovato negli esempi sono quasi identici a quelli in Java.

Ho ragione nei miei presupposti? & Quot; :: " è come appartiene a, e l'elenco dopo params e body sono come "quot args predefiniti" o qualcosa del genere?

UPDATE: Grazie per le risposte Possono essere chiamati metodi? (Immagino di no) e qual è la differenza nel chiamarli all'interno del corpo del costruttore

È stato utile?

Soluzione

Il caso più comune è questo:

class foo{
private:
    int x;
    int y;
public:
    foo(int _x, int _y) : x(_x), y(_y) {}
}

Questo imposterà x e y sui valori indicati in _x e _y nel costruttore parametri. Questo è spesso il modo migliore per costruire qualsiasi oggetto dichiarato come membro di dati.

È anche possibile che tu stia guardando il concatenamento del costruttore:

class foo : public bar{
    foo(int x, int y) : bar(x, y) {}
};

In questo caso, il costruttore della classe chiamerà il costruttore della sua classe base e passerà i valori x e y .

Per analizzare ulteriormente la funzione:

TransparentObject::TransparentObject( int w, int x, int y, int z ) : 
   _someMethod( 0 ),
   _someOtherMethod( 0 ),
   _someOtherOtherMethod( 0 ),
   _someMethodX( 0 ) 
{
     int bla;
     int bla;
}

L'operatore :: è chiamato operatore di risoluzione dell'ambito. Indica semplicemente che TransparentObject è un membro di TransparentObject . In secondo luogo, hai ragione a supporre che il corpo del costruttore si presenti tra parentesi graffe.

  

AGGIORNAMENTO: grazie per le risposte. Possono essere chiamati metodi? (Immagino di no) e qual è la differenza nel chiamarli all'interno del corpo del costruttore

Ci sono molte più informazioni su questo argomento di quante io possa mai darti qui . L'area più comune in cui è necessario utilizzare gli elenchi di inizializzatori è quando si inizializza un riferimento o una const poiché a queste variabili deve essere assegnato un valore immediatamente dopo la creazione.

Altri suggerimenti

Sei abbastanza vicino. La prima riga è la dichiarazione. L'etichetta a sinistra di :: è il nome della classe e per essere un costruttore, il nome della funzione deve essere uguale al nome della classe.

TransparentObject::TransparentObject( int w, int x, int y, int z )

In C ++ è possibile inserire facoltativamente due punti e alcuni valori iniziali per le variabili membro prima dell'inizio del corpo della funzione. Questa tecnica deve essere utilizzata se stai inizializzando qualsiasi const o passando parametri a un costruttore di superclassi.

: 
 _someMethod( 0 ),
 _someOtherMethod( 0 ),
 _someOtherOtherMethod( 0 ),
 _someMethodX( 0 )

E poi arriva il corpo del costruttore tra parentesi graffe.

{
   int bla;
   int bla;
}

:: In realtà significa contiene (vedere i commenti per chiarimenti), tuttavia _someMethods e così via è ciò che viene chiamato elenco di inizializzazione . Ci sono molte informazioni al link =]

EDIT: mi dispiace, la mia prima frase non è corretta - vedi i commenti.

Sì, :: è l'operatore di scoping C ++ che consente di indicare al compilatore a quale funzione appartiene. Usando un: dopo che la dichiarazione del costruttore inizia quello che viene chiamato un elenco di inizializzazione.

Il codice tra l'elenco degli argomenti e il {} specifica l'inizializzazione di (alcuni dei) membri della classe.

Inizializzazione in contrapposizione all'assegnazione --- sono cose diverse --- quindi queste sono tutte chiamate ai costruttori.

Hai ragione. È un modo per impostare i valori predefiniti per le variabili di classe. Non ho troppa familiarità con la differenza esatta tra metterli dopo: e nel corpo della funzione.

Di solito ci sono alcuni buoni motivi per usare un elenco di inizializzazione. Per uno, non è possibile impostare variabili membro che sono riferimenti al di fuori dell'elenco di inizializzazione del costruttore. Inoltre, se una variabile membro necessita di determinati argomenti per il proprio costruttore, è necessario passarli qui. Confronta questo:

class A
{
public:
  A();
private:
  B _b;
  C& _c;
};

A::A( C& someC )
{
  _c = someC; // this is illegal and won't compile. _c has to be initialized before we get inside the braces
  _b = B(NULL, 5, "hello"); // this is not illegal, but B might not have a default constructor or could have a very 
                            // expensive construction that shouldn't be done more than once
}

a questa versione:

A::A( C& someC )
: _b(NULL, 5, "hello") // ok, initializing _b by passing these arguments to its constructor
, _c( someC ) // this reference to some instance of C is correctly initialized now
{}

Senza utilizzare l'elenco di inizializzatori, tutti i membri della classe avranno semplicemente il loro costruttore predefinito chiamato, quindi questo è l'unico posto in cui puoi controllare quale costruttore (per membri allocati in modo non dinamico). Lo stesso vale per il quale verrà chiamato il costruttore della classe genitore.

Membri della classe " inizializzati " all'interno del corpo del costruttore (ovvero tra le parentesi graffe {} utilizzando l'operatore =) non è tecnicamente inizializzazione, è un compito. Per le classi con un costruttore / distruttore non banale può essere costoso il costrutto predefinito e quindi modificarlo tramite l'assegnazione in questo modo. Per i membri di riferimento devi utilizzare l'elenco di inizializzatori poiché non possono essere modificati tramite l'operatore di assegnazione.

Se il membro (o la classe genitore) non ha un costruttore predefinito, non riuscire a specificare un costruttore appropriato nell'elenco di inizializzatori causerà un errore nel compilatore. Altrimenti il ??compilatore inserirà le chiamate predefinite del costruttore stesso. Per i tipi predefiniti questo non fa nulla, quindi avrai valori di immondizia lì.

Notare che l'ordine in cui si specificano i membri nell'elenco inizializzatore non influisce sull'ordine in cui vengono chiamati . È sempre prima il costruttore della classe genitore (se presente), quindi i membri della classe nell'ordine in cui sono definiti nella definizione della classe. L'ordine in cui li metti nella lista degli inizializzatori non ha importanza e può essere la fonte di bug sottili ...

Nell'esempio inventato di seguito sembra che l'intenzione sia quella di inizializzare m_b con value quindi m_a con m_b , ma ciò che effettivamente accade è che m_a è inizializzato con m_b (che non è ancora inizializzato), quindi m_b viene inizializzato con valore . m_b conterrà semplicemente immondizia!

struct BadInitialiserListExample
{
    BadInitialiserListExample(int value) :
        m_b(value),
        m_a(m_b)      // <-- *BUG* this is actually executed first due to ordering below!
    {
    }

    int    m_a;
    int    m_b;
};
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top