Perché dovrei preferire l’utilizzo dell’elenco di inizializzazione dei membri?

Sono parziale nell’utilizzare le liste di inizializzazione dei membri con i miei costruttori … ma ho dimenticato da tempo le ragioni di questo …

Utilizzi gli elenchi di inizializzazione dei membri nei tuoi costruttori? Se è così, perché? Se no, perché no?

Per i membri della class POD , non fa differenza, è solo una questione di stile. Per i membri della class che sono classi, evita una chiamata non necessaria a un costruttore predefinito. Prendere in considerazione:

class A { public: A() { x = 0; } A(int x_) { x = x_; } int x; }; class B { public: B() { ax = 3; } private: A a; }; 

In questo caso, il costruttore per B chiamerà il costruttore predefinito per A , e quindi inizializzerà ax su 3. Un modo migliore sarebbe che il costruttore di B inviti direttamente il costruttore di A nella lista di inizializzazione:

 B() : a(3) { } 

Questo chiamerebbe solo il costruttore A(int) e non il costruttore predefinito. In questo esempio, la differenza è trascurabile, ma immaginate se il costruttore di default di A abbia fatto di più, come allocare memoria o aprire file. Non vorrai farlo inutilmente.

Inoltre, se una class non ha un costruttore predefinito o una variabile membro const , è necessario utilizzare un elenco inizializzatore:

 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; }; 

A parte i motivi di prestazioni sopra menzionati, se la tua class memorizza riferimenti ad oggetti passati come parametri del costruttore o la tua class ha variabili const, non hai scelta se non utilizzare gli elenchi di inizializzatori.

  1. Inizializzazione della class base

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

Secondo l’ordine di costruzione, la class base dovrebbe essere costruita prima della class bambino. Senza l’elenco di inizializzatore del costruttore, questo è ansible se la class base ha un costruttore predefinito che verrà chiamato appena prima di entrare nel costruttore della class figlio.

Tuttavia, se la class base ha solo un costruttore con parametri, è necessario utilizzare l’elenco di inizializzatore del costruttore per garantire che la class base venga inizializzata prima della class figlio.

  1. Inizializzazione di suboggetti che hanno solo costruttori parametrizzati

  2. Efficienza

Utilizzando l’elenco di inizializzatore del costruttore, si inizializzano i membri dei dati nello stato esatto di cui si ha bisogno nel codice invece di inizializzarli al loro stato predefinito e quindi di modificare il loro stato in quello che è necessario nel codice.

  1. Inizializzazione dei membri di dati const non statici

Se i membri di dati const non statici nella class hanno costruttori predefiniti e non si utilizza l’elenco di inizializzatore di costruzione, non sarà ansible inizializzarli allo stato previsto poiché verranno inizializzati allo 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, in quanto i riferimenti non possono essere dichiarati e inizializzati in seguito. Questo è ansible solo con l’elenco di inizializzatore del costruttore.

Accanto ai problemi di prestazioni, ce n’è un altro molto importante che chiamerei manutenibilità ed estendibilità del codice.

Se un T è POD e inizi a preferire l’elenco di inizializzazione, allora se T una volta cambierà in un tipo non POD, non dovrai modificare nulla attorno all’inizializzazione per evitare chiamate di costruttori non necessarie perché è già ottimizzato.

Se il tipo T ha un costruttore predefinito e uno o più costruttori definiti dall’utente e una volta si decide di rimuovere o hide 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 i membri di riferimento, supponiamo che inizialmente T sia definito come segue:

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

Successivamente, si decide di qualificare un const come, se si usasse la lista di inizializzazione dall’inizio, allora si trattava di una singola riga di cambiamento, 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 un “codice scimmia” ma da un ingegnere che prende decisioni basate su una considerazione più profonda su ciò che sta facendo.

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

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

 // 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 object di tipo MyClass
1. Il costruttore del tipo viene chiamato prima per “a”.
2. L’operatore di assegnazione di “Tipo” viene chiamato all’interno del costruttore di BodyClass () per l’assegnazione

 variable = a; 
  1. E infine il distruttore di “Tipo” viene chiamato per “a” poiché non rientra nello scope.

    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 inizializzatore, i seguenti passaggi sono seguiti dal compilatore:

    1. Il costruttore di copia della class “Tipo” viene chiamato per inizializzare: variabile (a). Gli argomenti nella lista di inizializzazione sono usati per copiare direttamente la “variabile” del costrutto.
    2. Il distruttore di “Tipo” viene chiamato per “a” poiché non rientra nell’ambito.

Sintassi:

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

Elenco delle necessità di inizializzazione:

  class Sample { public: int Sam_x; int Sam_y; Sample() */* Object and variables are created - ie:declaration of variables */* { // Constructor body starts Sam_x = 1; */* Defining a value to the variable */* Sam_y = 2; } // Constructor body ends }; 

nel programma precedente, quando viene eseguito il costruttore della class, vengono creati Sam_x e Sam_y . Quindi nel corpo del costruttore, vengono definite le variabili dei dati membro.

Casi d’uso:

  1. Costanti e variabili di riferimento in una class

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’object usando l’elenco di inizializzazione. se eseguiamo l’inizializzazione dopo la creazione dell’object (nel corpo del costruttore interno), otterremo l’errore del tempo di compilazione.

  1. Oggetti membro della class 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 dell’object per la class derivata che chiamerà internamente il costruttore della class derivata e chiama il costruttore della class base (predefinito). se la class base non ha un costruttore predefinito, l’utente riceverà un errore in fase 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 class e il membro Data di una class 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à della variabile globale se entrambe le variabili hanno lo stesso nome. In questo caso, il programma considera il valore “i” {variabile sia destra che sinistra. vale a dire: i = i} come variabile locale nel costruttore Sample3 () e la variabile membro della class (i) ha l’override. Per evitare, dobbiamo usare entrambi

  1. Initialization list 2. this operator. 

Solo per aggiungere alcune informazioni aggiuntive per dimostrare quanta differenza può fare l’elenco di inizializzazione dei membri . Nel codecode 303 Range Sum Query – Immutable, https://leetcode.com/problems/range-sum-query-immutable/ , dove è necessario build e inizializzare a zero un vettore con determinate dimensioni. Ecco due diverse implementazioni e confronti di velocità.

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

 class NumArray { public: vector preSum; NumArray(vector nums) { preSum = vector(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 usando la lista di inizializzazione dei membri , il tempo per ottenere AC è di circa 108 ms . Con questo semplice esempio, è abbastanza ovvio che l' elenco di inizializzazione dei membri è molto più efficiente . Tutta la misura è dal tempo di esecuzione da LC.

 class NumArray { public: vector preSum; NumArray(vector 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]; } };