Perché dovrei preferire utilizzare l'elenco di inizializzazione dei membri?


Risposte:


278

Per i membri della classe POD , non fa differenza, è solo una questione di stile. Per i membri della classe che sono classi, quindi evita una chiamata non necessaria a un costruttore predefinito. Tener conto di:

class A
{
public:
    A() { x = 0; }
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B()
    {
        a.x = 3;
    }
private:
    A a;
};

In questo caso, il costruttore per Bchiamerà il costruttore predefinito per A, quindi inizializzerà a.xsu 3. Un modo migliore sarebbe che Bil costruttore chiamasse direttamente Ail costruttore nell'elenco di inizializzatori:

B()
  : a(3)
{
}

Questo chiamerebbe solo Ail A(int)costruttore e non il costruttore predefinito. In questo esempio, la differenza è trascurabile, ma immagina se il Acostruttore predefinito ha fatto di più, ad esempio allocando memoria o aprendo i file. Non vorrai farlo inutilmente.

Inoltre, se una classe non ha un costruttore predefinito o si dispone di una constvariabile membro, è necessario utilizzare un elenco di inizializzatori:

class A
{
public:
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B() : a(3), y(2)  // 'a' and 'y' MUST be initialized in an initializer list;
    {                 // it is an error not to do so
    }
private:
    A a;
    const int y;
};

5
un must è anche un caso importante di riferimento
4pie0

5
Perché non usare "a (3);" o "a = A (3);" nel corpo del costruttore predefinito di B?
Sergey

1
Potresti spiegare cosa intendi con POD?
Jonas Stein,

2
@JonasStein POD è un insieme ben definito di regole relative a semplici strutture di dati (piuttosto che a classi complete). Leggi le FAQ per ulteriori: stackoverflow.com/questions/146452/what-are-pod-types-in-c
monkey0506

2
@Sergey, il costruttore predefinito di A sarebbe ancora chiamato.
Vassilis,

44

A parte i motivi di prestazione sopra menzionati, se la tua classe memorizza riferimenti ad oggetti passati come parametri del costruttore o la tua classe ha variabili const, allora non hai altra scelta se non usare gli elenchi di inizializzatori.


7
Lo stesso vale per i membri const che credo.
Richard Corden,

sì, non è possibile utilizzare l'assegnazione per modificare le variabili const, quindi deve essere inizializzata.
Hareen Laks,

23
  1. Inizializzazione della classe base

Un motivo importante per l'utilizzo dell'elenco di inizializzatori del costruttore che non è menzionato nelle risposte qui è l'inizializzazione della classe base.

Secondo l'ordine di costruzione, la classe base dovrebbe essere costruita prima della classe figlio. Senza l'elenco di inizializzatori del costruttore, questo è possibile se la classe base ha un costruttore predefinito che verrà chiamato poco prima di entrare nel costruttore della classe figlio.

Tuttavia, se la classe base ha solo un costruttore con parametri, è necessario utilizzare l'elenco di inizializzatori del costruttore per assicurarsi che la classe base sia inizializzata prima della classe figlio.

  1. Inizializzazione di oggetti secondari che hanno solo costruttori parametrizzati

  2. Efficienza

Utilizzando l'elenco di inizializzatori del costruttore, si inizializzano i membri dei dati in modo da specificare lo stato necessario nel proprio codice anziché inizializzarli prima nel loro stato predefinito e quindi cambiando il loro stato con quello necessario nel proprio codice.

  1. Inizializzazione di membri di dati const non statici

Se i membri di dati const non statici nella tua classe hanno costruttori predefiniti e non usi l'elenco di inizializzatori del costruttore, non sarai in grado di inizializzarli allo stato previsto poiché verranno inizializzati al loro stato predefinito.

  1. Inizializzazione dei membri dei dati di riferimento

I membri dei dati di riferimento devono essere inizializzati quando il compilatore entra nel costruttore poiché i riferimenti non possono essere dichiarati e inizializzati in un secondo momento. Questo è possibile solo con l'elenco di inizializzatori del costruttore.


10

Oltre ai problemi di prestazioni, ce n'è un altro molto importante che definirei manutenibilità ed estensibilità del codice.

Se un T è POD e inizi a preferire l'elenco di inizializzazione, quindi se una volta T cambierà in un tipo non POD, non dovrai cambiare nulla attorno all'inizializzazione per evitare inutili chiamate del costruttore perché è già ottimizzato.

Se il tipo T ha un costruttore predefinito e uno o più costruttori definiti dall'utente e una volta decidi di rimuovere o nascondere quello predefinito, quindi se è stato utilizzato l'elenco di inizializzazione, non è necessario aggiornare il codice se i costruttori definiti dall'utente perché sono già correttamente implementati.

Lo stesso vale per i membri const o di riferimento, supponiamo che inizialmente T sia definito come segue:

struct T
{
    T() { a = 5; }
private:
    int a;
};

Successivamente, decidi di qualificare un come const, se utilizzassi l'elenco di inizializzazione dall'inizio, quindi si trattava di una modifica a riga singola, ma avendo la T definita come sopra, richiede anche di scavare la definizione del costruttore per rimuovere l'assegnazione:

struct T
{
    T() : a(5) {} // 2. that requires changes here too
private:
    const int a; // 1. one line change
};

Non è un segreto che la manutenzione sia molto più semplice e meno soggetta a errori se il codice non è stato scritto da una "scimmia di codice" ma da un ingegnere che prende decisioni sulla base di una più profonda considerazione di ciò che sta facendo.


5

Prima che venga eseguito il corpo del costruttore, vengono invocati tutti i costruttori per la sua classe padre e quindi per i suoi campi. Per impostazione predefinita, vengono richiamati i costruttori senza argomento. Gli elenchi di inizializzazione consentono di scegliere quale costruttore viene chiamato e quali argomenti riceve tale costruttore.

Se si dispone di un campo di riferimento o const o se una delle classi utilizzate non ha un costruttore predefinito, è necessario utilizzare un elenco di inizializzazione.


2
// Without Initializer List
class MyClass {
    Type variable;
public:
    MyClass(Type a) {  // Assume that Type is an already
                     // declared class and it has appropriate 
                     // constructors and operators
        variable = a;
    }
};

Qui il compilatore segue i seguenti passi per creare un oggetto di tipo MyClass
1. Il costruttore del tipo viene chiamato per primo per "a".
2. L'operatore di assegnazione di "Tipo" viene chiamato all'interno del costruttore MyClass () da assegnare

variable = a;
  1. E infine il distruttore di "Tipo" viene chiamato per "a" poiché non rientra nel campo di applicazione.

    Ora considera lo stesso codice con il costruttore MyClass () con Initializer List

    // With Initializer List
     class MyClass {
    Type variable;
    public:
    MyClass(Type a):variable(a) {   // Assume that Type is an already
                     // declared class and it has appropriate
                     // constructors and operators
    }
    };

    Con l'elenco di inizializzatori, il compilatore segue i seguenti passaggi:

    1. Il costruttore della copia della classe "Tipo" viene chiamato per inizializzare: variabile (a). Gli argomenti nell'elenco di inizializzatori vengono utilizzati per copiare direttamente il costrutto "variabile".
    2. Il distruttore di "Tipo" è chiamato per "a" poiché non rientra nell'ambito.

2
Sebbene questo frammento di codice possa risolvere la domanda, inclusa una spiegazione fuori dal codice aiuta davvero a migliorare la qualità del tuo post. Ricorda che stai rispondendo alla domanda per i lettori in futuro e quelle persone potrebbero non conoscere i motivi del tuo suggerimento sul codice. Cerca inoltre di non aggiungere il tuo codice a commenti esplicativi, ciò riduce la leggibilità sia del codice che delle spiegazioni! meta.stackexchange.com/q/114762/308249
davejal

2
Per favore, scrivi la tua comprensione o condividi semplicemente il link alla fonte originale (qui, geeksforgeeks.com) invece di incollarlo e copiarlo.
yuvi,

1

Solo per aggiungere alcune informazioni aggiuntive per dimostrare quanta differenza può fare la lista di inizializzazione dei membri . Nella query somma somma 303 303 - Immutabile, https://leetcode.com/problems/range-sum-query-immutable/ , dove è necessario costruire e inizializzare per azzerare un vettore con determinate dimensioni. Ecco due differenti implementazioni e confronti di velocità.

Senza l' elenco di inizializzazione dei membri , ottenere AC mi è costato circa 212 ms .

class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) {
    preSum = vector<int>(nums.size()+1, 0);
    int ps = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        ps += nums[i];
        preSum[i+1] = ps;
    }
}

int sumRange(int i, int j) {
    return preSum[j+1] - preSum[i];
}
};

Ora utilizzando l'elenco di inizializzazione dei membri , il tempo per ottenere CA è di circa 108 ms . Con questo semplice esempio, è abbastanza ovvio che l' elenco di inizializzazione dei membri è molto più efficiente . Tutta la misurazione proviene dal tempo di esecuzione da LC.

class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) : preSum(nums.size()+1, 0) { 
    int ps = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        ps += nums[i];
        preSum[i+1] = ps;
    }
}

int sumRange(int i, int j) {
    return preSum[j+1] - preSum[i];
}
};

0

Sintassi:

  class Sample
  {
     public:
         int Sam_x;
         int Sam_y;

     Sample(): Sam_x(1), Sam_y(2)     /* Classname: Initialization List */
     {
           // Constructor body
     }
  };

Necessità dell'elenco di inizializzazione:

 class Sample
 {
     public:
         int Sam_x;
         int Sam_y;

     Sample()     */* Object and variables are created - i.e.:declaration of variables */*
     { // Constructor body starts 

         Sam_x = 1;      */* Defining a value to the variable */* 
         Sam_y = 2;

     } // Constructor body ends
  };

nel programma sopra, Quando viene eseguito il costruttore della classe, vengono creati Sam_x e Sam_y . Quindi, nel corpo del costruttore, vengono definite quelle variabili dei dati dei membri.

Casi d'uso:

  1. Variabili costanti e di riferimento in una classe

In C, le variabili devono essere definite durante la creazione. allo stesso modo in C ++, dobbiamo inizializzare la variabile Const e Reference durante la creazione dell'oggetto usando l'elenco Inizializzazione. se eseguiamo l'inizializzazione dopo la creazione di un oggetto (all'interno del corpo del costruttore), avremo un errore di tempo di compilazione.

  1. Oggetti membri della classe Sample1 (base) che non hanno un costruttore predefinito

     class Sample1 
     {
         int i;
         public:
         Sample1 (int temp)
         {
            i = temp;
         }
     };
    
      // Class Sample2 contains object of Sample1 
     class Sample2
     {
      Sample1  a;
      public:
      Sample2 (int x): a(x)      /* Initializer list must be used */
      {
    
      }
     };

Durante la creazione di oggetti per la classe derivata che chiamerà internamente il costruttore della classe derivata e chiamerà il costruttore della classe base (impostazione predefinita). se la classe base non ha un costruttore predefinito, l'utente riceverà un errore di tempo di compilazione. Per evitare, dobbiamo avere entrambi

 1. Default constructor of Sample1 class
 2. Initialization list in Sample2 class which will call the parametric constructor of Sample1 class (as per above program)
  1. Il nome del parametro del costruttore della classe e il membro Data di una classe sono gli stessi:

     class Sample3 {
        int i;         /* Member variable name : i */  
        public:
        Sample3 (int i)    /* Local variable name : i */ 
        {
            i = i;
            print(i);   /* Local variable: Prints the correct value which we passed in constructor */
        }
        int getI() const 
        { 
             print(i);    /*global variable: Garbage value is assigned to i. the expected value should be which we passed in constructor*/
             return i; 
        }
     };

Come tutti sappiamo, la variabile locale ha la massima priorità rispetto alla variabile globale se entrambe le variabili hanno lo stesso nome. In questo caso, il programma considera il valore "i" {variabile sia sinistra che destra. vale a dire: i = i} come variabile locale nel costruttore Sample3 () e la variabile membro della classe (i) ha avuto la precedenza. Per evitare, dobbiamo usare entrambi

  1. Initialization list 
  2. this operator.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.