Quando devo utilizzare gli elenchi di inizializzatori per inizializzare i membri della classe C ++?

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

  •  06-07-2019
  •  | 
  •  

Domanda

diciamo che ho     std::map< std::string, std::string > m_someMap come variabile membro privata di classe A

Due domande: (e l'unica ragione per cui mi sto chiedendo è perché mi sono imbattuto in un codice del genere)

  1. Qual è lo scopo di questa linea:

    A::A() : m_someMap()
    

    Ora so che si tratta di inizializzazione, ma devi farlo in questo modo? Sono confuso.

  2. Qual è il valore predefinito di <=>, anche C # definisce che int, double, ecc. è sempre inizializzato su 0 e gli oggetti sono nulli (almeno nella maggior parte dei casi) Quindi qual è la regola in C ++ ?? gli oggetti sono inizializzati da defualt a null e le primitive a garbage? Ovviamente sto prendendo in considerazione le variabili di istanza.

EDIT:

inoltre, poiché la maggior parte delle persone ha sottolineato che questa è una scelta di stile e non necessaria, che dire di:

A :: A (): m_someMap (), m_someint (0), m_somebool (false)

È stato utile?

Soluzione

m_somemap

  1. Non è necessario.
  2. Cosa ottieni se lo ometti: un std::map< std::string, std::string > vuoto, ovvero un'istanza valida di quella mappa che non contiene elementi.

m_somebool

  1. È necessario inizializzarlo su true o false se si desidera che abbia un valore noto. I booleani sono & Quot; semplici vecchi tipi di dati & Quot; e non hanno il concetto di costruttore. Inoltre, il linguaggio C ++ non specifica i valori predefiniti per i booleani non inizializzati in modo esplicito.
  2. Cosa ottieni se lo ometti: un membro booleano con un valore non specificato. Non devi farlo e successivamente utilizzarne il valore. Per questo motivo, è fortemente consigliato inizializzare tutti i valori di questo tipo.

m_someint

  1. È necessario inizializzarlo su un valore intero se si desidera che abbia un valore noto. I numeri interi sono & Quot; semplici vecchi tipi di dati & Quot; e non hanno il concetto di costruttore. Inoltre, il linguaggio C ++ non specifica i valori predefiniti per numeri interi non esplicitamente inizializzati.
  2. Cosa ottieni se lo ometti: un membro int con un valore non specificato. Non devi farlo e successivamente utilizzarne il valore. Per questo motivo, è fortemente consigliato inizializzare tutti i valori di questo tipo.

Altri suggerimenti

Non è necessario farlo effettivamente.
Il costruttore predefinito lo farà automaticamente.

Ma a volte rendendolo esplicito agisce come una sorta di documentazione:

class X
{
    std::map<string,string>  data;
    Y                        somePropertyOfdata;

    X()
      :data()                    // Technically not needed
      ,somePropertyOfdata(data)  // But it documents that data is finished construction
    {}                           // before it is used here.
};

La regola in C ++ è che, a meno che non inizializzi esplicitamente i dati POD, questi non sono definiti mentre altre classi hanno un costruttore predefinito chiamato automaticamente (anche se non esplicitamente fatto dal programmatore).

Ma dicendo questo. Considera questo:

template<typename T>
class Z
{
     T  data;   
     Z()
        :data()    // Technicall not need as default constructor will
                   // always be called for classes.
                   // But doing this will initialize POD data correctly
                   // if T is a basic POD type. 
     {}
};

Qui dovresti sospettare che i dati siano inizializzati di default.
Tecnicamente il POD non ha costruttori quindi se T fosse int ti aspetteresti che faccia qualcosa? Poiché è stato inizializzato esplicitamente, è impostato su 0 o equivalente per i tipi POD.

Per la modifica:

class A
{
    std::map<string,string>   m_someMap;
    int                       m_someint;
    bool                      m_somebool;
   public:
    A::A()
       : m_someMap()      // Class will always be initialised (so optional)
       , m_someint(0)     // without this POD will be undefined
       , m_somebool(false)// without this POD will be undefined
    {}
};

Come altri hanno sottolineato: non è necessario ma più o meno una questione di stile. Il lato positivo: mostra che vuoi esplicitamente usare il costruttore predefinito e rende il tuo codice più dettagliato. Il rovescio della medaglia: se hai più di un ctor, può essere una seccatura mantenere i cambiamenti in tutti loro e, a volte, aggiungi membri della classe e dimentichi di aggiungerli all'elenco degli inizializzatori ctors e renderlo incoerente.

A::A() : m_someMap()

Questa riga non è necessaria in questo caso. Tuttavia, in generale , è l'unico modo corretto per inizializzare i membri della classe.

Se hai un costruttore come questo:

X() : y(z) {
 w = 42;
}

quindi quando si chiama il costruttore X si verifica quanto segue:

  • Innanzitutto, tutti i membri sono inizializzati: per y, diciamo esplicitamente che desideriamo chiamare il costruttore che prende z come argomento. Per w, ciò che accade dipende dal tipo di :. Se m_someMap è un tipo POD (ovvero sostanzialmente un tipo C compatibile: nessuna eredità, nessun costruttore o distruttore, tutti i membri pubblici e anche tutti i membri sono tipi POD), allora è non inizializzato. Il suo valore iniziale è qualunque immondizia trovata a quell'indirizzo di memoria. Se : m_someMap() è un tipo non POD, viene chiamato il suo costruttore predefinito (i tipi non POD sono sempre inizializzati durante la costruzione).
  • Una volta che entrambi i membri sono stati costruiti, quindi chiamiamo l'operatore di assegnazione per assegnare 42 a <=>.

La cosa importante da notare è che tutti i costruttori sono chiamati prima che entriamo nel corpo del costruttore. Una volta che siamo nel corpo, tutti i membri sono già stati inizializzati. Quindi ci sono due possibili problemi con il nostro corpo di costruttore.

  • Cosa succede se <=> è di un tipo che non ha un costruttore predefinito? Quindi questo non verrà compilato. Quindi deve essere inizializzato esplicitamente dopo <=>, come <=>.
  • Cosa succede se questa sequenza di chiamata sia costruttore predefinito che operatore di assegnazione è inutilmente lenta? Forse sarebbe molto più efficiente chiamare semplicemente il costruttore corretto per cominciare.

Quindi in breve, dal momento che <=> è un tipo non POD, non dobbiamo rigorosamente parlare <=>. Sarebbe stato comunque costruito di default. Ma se fosse stato un tipo POD o se avessimo voluto chiamare un costruttore diverso da quello predefinito, avremmo dovuto farlo.

Giusto per essere chiari su ciò che sta accadendo (per quanto riguarda la tua seconda domanda)

std::map< std::string, std::string > m_someMap crea una variabile di stack chiamata m_someMap e su di essa viene chiamato il costruttore predefinito. La regola per C ++ per tutti i tuoi oggetti è se vai:

T varName;

dove T è un tipo, varName è costruito per impostazione predefinita.

T* varName;

dovrebbe essere esplicitamente assegnato a NULL (o 0) - o nullptr nel nuovo standard.

Per chiarire il problema del valore predefinito:

C ++ non ha il concetto che alcuni tipi di implicità siano per riferimento. A meno che qualcosa non sia esplicitamente dichiarato come puntatore, non può mai assumere un valore nullo. Ciò significa che ogni avrà un costruttore predefinito per la creazione del valore iniziale quando non vengono specificati parametri del costruttore. Se non viene dichiarato alcun costruttore predefinito, il compilatore ne genererà uno per te. Inoltre, ogni volta che una classe contiene membri di tipo classificato, tali membri verranno inizializzati implicitamente tramite i loro costruttori predefiniti nella costruzione dell'oggetto, a meno che usi la sintassi dei due punti per chiamare esplicitamente un costruttore diverso.

Accade semplicemente che il costruttore predefinito per tutti i tipi di contenitore STL costruisca un contenitore vuoto. Altre classi potrebbero avere altre convenzioni per ciò che fanno i loro costruttori predefiniti, quindi vuoi comunque essere consapevole che vengono invocate in situazioni come questa. Ecco perché la A::A() : m_someMap() riga, che in realtà sta solo dicendo al compilatore di fare quello che farebbe già comunque.

Quando si crea un oggetto in C ++, il costruttore esegue la seguente sequenza:

  1. Chiama i costruttori di tutte le classi virtuali principali nell'intero albero delle classi (in un ordine arbitrario)
  2. Chiama i costruttori di tutte le classi padre ereditate direttamente nell'ordine di dichiarazione
  3. Chiama i costruttori di tutte le variabili membro nell'ordine di dichiarazione

Ci sono alcuni dettagli in più di questo, e alcuni compilatori ti permettono di forzare alcune cose fuori da questo ordine specifico, ma questa è l'idea generale. Per ciascuna di queste chiamate del costruttore è possibile specificare gli argomenti del costruttore, nel qual caso C ++ chiamerà il costruttore come specificato, oppure è possibile lasciarlo solo e C ++ proverà a chiamare il costruttore predefinito. Il costruttore predefinito è semplicemente quello che non accetta argomenti.

Se una qualsiasi delle tue classi genitore virtuale, delle classi genitore non virtuale o delle variabili membro non ha un costruttore predefinito o deve essere creata con qualcosa di diverso da quello predefinito, le aggiungi all'elenco delle chiamate del costruttore. Poiché C ++ presuppone una chiamata del costruttore predefinita, non vi è assolutamente alcuna differenza tra l'inserimento di un costruttore predefinito nell'elenco e la sua esclusione totale (il C ++ non (a meno che in circostanze speciali al di fuori dell'ambito di questa domanda) crei un oggetto senza una chiamata a un costruttore di qualche tipo). Se una classe non ha un costruttore predefinito e non si fornisce una chiamata al costruttore, il compilatore genererà un errore.

Quando si tratta di tipi predefiniti come float o int, il costruttore predefinito non fa nulla, e quindi la variabile avrà il valore predefinito di tutto ciò che è rimasto nel pezzo di memoria. Tutti i tipi incorporati hanno anche un costruttore di copia, quindi puoi inizializzarli passando il loro valore iniziale come unico argomento al costruttore della variabile.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top