Come vengono inizializzati i membri della classe C ++ se non lo faccio esplicitamente?


158

Supponiamo che io ho una classe con memebers privati ptr, name, pname, rname, crnamee age. Cosa succede se non li inizializzo da solo? Ecco un esempio:

class Example {
    private:
        int *ptr;
        string name;
        string *pname;
        string &rname;
        const string &crname;
        int age;

    public:
        Example() {}
};

E poi faccio:

int main() {
    Example ex;
}

Come vengono inizializzati i membri in ex? Cosa succede con i puntatori? Do stringe intottenere 0-inizializzata con costruttori di default string()e int()? E il membro di riferimento? E i riferimenti const?

Cos'altro dovrei sapere?

Qualcuno conosce un tutorial che copre questi casi? Forse in alcuni libri? Ho accesso alla biblioteca dell'università a molti libri C ++.

Mi piacerebbe impararlo in modo da poter scrivere programmi migliori (senza bug). Qualsiasi feedback sarebbe d'aiuto!


3
Per consigli sui libri, consultare stackoverflow.com/questions/388242/…
Mike Seymour,

Mike, intendo il capitolo di un libro che lo spiega. Non libro intero! :)
bodacydo,

Tuttavia, sarebbe probabilmente una buona idea leggere un intero libro su una lingua in cui si intende programmare. E se ne hai già letto uno e non lo ha spiegato, non è stato un ottimo libro.
Tyler McHenry,

2
Scott Meyers (un popolare guru del consiglio ex-professionista C ++) in Effective C ++ , "le regole sono complicate - troppo complicate per essere memorizzate, secondo me .... assicuratevi che tutti i costruttori inizializzino tutto nell'oggetto". Quindi, a suo parere, il modo più semplice per (tentare di) scrivere "bug" codice non è quello di cercare di memorizzare le regole (e in effetti lui non non li lay out nel libro), ma per esplicita inizializzazione tutto. Nota, tuttavia, che anche se segui questo approccio nel tuo codice, potresti lavorare su progetti scritti da persone che non lo fanno, quindi le regole potrebbero essere comunque valide.
Kyle Strand,

2
@TylerMcHenry Quali libri su C ++ consideri "buoni"? Ho letto diversi libri su C ++, ma nessuno di loro lo ha spiegato completamente. Come notato nel mio commento precedente, Scott Meyers rifiuta esplicitamente di fornire le regole complete in C ++ efficace . Ho anche letto Effective Modern C ++ di Meyers , C ++ Common Knowledge di Dewhurst e A Tour of C ++ di Stroustrup . A mio ricordo, nessuno di loro ha spiegato le regole complete. Ovviamente avrei potuto leggere lo standard, ma difficilmente lo considero un "buon libro"! : D E mi aspetto che Stroustrup probabilmente lo spieghi nel linguaggio di programmazione C ++ .
Kyle Strand

Risposte:


207

Al posto dell'inizializzazione esplicita, l'inizializzazione dei membri nelle classi funziona in modo identico all'inizializzazione delle variabili locali nelle funzioni.

Per gli oggetti , viene chiamato il loro costruttore predefinito. Ad esempio, per std::string, il costruttore predefinito lo imposta su una stringa vuota. Se la classe dell'oggetto non ha un costruttore predefinito, sarà un errore di compilazione se non lo si inizializza esplicitamente.

Per i tipi primitivi (int, puntatori, ecc), sono non inizializzati - contengono tutto ciò spazzatura arbitraria è capitato di essere in quella posizione di memoria in precedenza.

Per i riferimenti (ad es. std::string&), È illegale non inizializzarli e il compilatore si lamenterà e rifiuterà di compilare tale codice. I riferimenti devono sempre essere inizializzati.

Quindi, nel tuo caso specifico, se non sono esplicitamente inizializzati:

    int *ptr;  // Contains junk
    string name;  // Empty string
    string *pname;  // Contains junk
    string &rname;  // Compile error
    const string &crname;  // Compile error
    int age;  // Contains junk

4
+1. Vale la pena notare che, secondo la rigorosa definizione standard, le istanze di tipi primitivi insieme a una varietà di altre cose (qualsiasi regione di archiviazione) sono tutti considerati oggetti .
puzzolente472

7
"Se la classe dell'oggetto non ha un costruttore predefinito, sarà un errore di compilazione se non lo inizializzi esplicitamente" è sbagliato ! Se una classe non ha un costruttore predefinito, viene fornito un costruttore predefinito predefinito che è vuoto.
Mago

19
@wiz Penso che intendesse letteralmente "se l'oggetto non ha un costruttore predefinito" come in nessuno dei due generati, il che sarebbe il caso se la classe definisse esplicitamente qualsiasi costruttore diverso da quello predefinito (non verrà generato alcun ctor predefinito). Se diventiamo troppo pedanatici, probabilmente confonderemo più dell'aiuto e Tyler fa un buon punto su questo nella sua risposta a me prima.
puzzolente472

8
@ wiz-loz Direi che fooha un costruttore, è solo implicito. Ma questo è davvero un argomento di semantica.
Tyler McHenry,

4
Interpreto "costruttore predefinito" come costruttore che può essere chiamato senza argomenti. Questo sarebbe uno che definisci te stesso o generato implicitamente dal compilatore. Quindi la sua mancanza non significa né definito da te stesso né generato. O è così che lo vedo.
5

28

Prima di tutto, lasciami spiegare cos'è un elenco di inizializzatori di mem . Un elenco di inizializzatori mem è un elenco separato da virgole di inizializzatori mem , in cui ogni inizializzatore mem è un nome membro seguito da (, seguito da un elenco espressioni , seguito da un ). L' elenco di espressioni è il modo in cui il membro è costruito. Ad esempio, in

static const char s_str[] = "bodacydo";
class Example
{
private:
    int *ptr;
    string name;
    string *pname;
    string &rname;
    const string &crname;
    int age;

public:
    Example()
        : name(s_str, s_str + 8), rname(name), crname(name), age(-4)
    {
    }
};

l' elenco mem-inizializzatore del costruttore fornito dall'utente, senza argomenti name(s_str, s_str + 8), rname(name), crname(name), age(-4). Questo mem-inizializzazione-list significa che il namemembro è inizializzato il std::stringcostruttore che richiede due iteratori di ingresso , il rnamemembro viene inizializzato con un riferimento name, il crnamemembro è inizializzato con un const riferimento name, e l' ageelemento viene inizializzato con il valore -4.

Ogni costruttore ha il proprio elenco di inizializzatori mem e i membri possono essere inizializzati solo in un ordine prestabilito (sostanzialmente l'ordine in cui i membri sono dichiarati nella classe). Così, i membri Examplepossono essere inizializzate solo nell'ordine: ptr, name, pname, rname, crname, e age.

Quando non si specifica un inizializzatore mem di un membro, lo standard C ++ dice:

Se l'entità è un membro di dati non statico ... di tipo di classe ..., l'entità è inizializzata per impostazione predefinita (8.5). ... Altrimenti, l'entità non è inizializzata.

Qui, poiché nameè un membro di dati non statico del tipo di classe, viene inizializzato per impostazione predefinita se nell'elenco inizializzatore mem non è namestato specificato alcun inizializzatore per . Tutti gli altri membri di non hanno tipo di classe, quindi non sono inizializzati.Example

Quando lo standard dice che non sono inizializzati, ciò significa che possono avere qualsiasi valore. Pertanto, poiché il codice sopra riportato non è stato inizializzato pname, potrebbe essere qualsiasi cosa.

Nota che devi ancora seguire altre regole, come la regola che i riferimenti devono sempre essere inizializzati. È un errore del compilatore non inizializzare i riferimenti.


Questo è il modo migliore per inizializzare i membri quando si desidera separare rigorosamente la dichiarazione (in .h) e la definizione (in .cpp) senza mostrare troppi interni.
Matthieu,

12

Puoi anche inizializzare i membri dei dati nel momento in cui li dichiari:

class another_example{
public:
    another_example();
    ~another_example();
private:
    int m_iInteger=10;
    double m_dDouble=10.765;
};

Uso questo modulo praticamente esclusivamente, anche se ho letto che alcune persone lo considerano "cattiva forma", forse perché è stato introdotto solo di recente - penso in C ++ 11. Per me è più logico.

Un altro aspetto utile delle nuove regole è come inizializzare i membri dei dati che sono essi stessi classi. Ad esempio supponiamo che CDynamicStringsia una classe che incapsula la gestione delle stringhe. Ha un costruttore che ti consente di specificare il suo valore iniziale CDynamicString(wchat_t* pstrInitialString). Si potrebbe benissimo usare questa classe come membro di dati all'interno di un'altra classe, ad esempio una classe che incapsula un valore di registro di Windows che in questo caso memorizza un indirizzo postale. Per "codificare" il nome della chiave di registro in cui viene scritto, utilizzare le parentesi graffe:

class Registry_Entry{
public:
    Registry_Entry();
    ~Registry_Entry();
    Commit();//Writes data to registry.
    Retrieve();//Reads data from registry;
private:
    CDynamicString m_cKeyName{L"Postal Address"};
    CDynamicString m_cAddress;
};

Nota che la seconda classe di stringhe che contiene l'indirizzo postale effettivo non ha un inizializzatore, quindi il suo costruttore predefinito verrà chiamato al momento della creazione, forse impostandolo automaticamente su una stringa vuota.


9

Se la classe di esempio viene istanziata nello stack, il contenuto dei membri scalari non inizializzati è casuale e non definito.

Per un'istanza globale, i membri scalari non inizializzati verranno azzerati.

Per i membri che sono essi stessi istanze di classi, verranno chiamati i loro costruttori predefiniti, quindi l'oggetto stringa verrà inizializzato.

  • int *ptr; // puntatore non inizializzato (o azzerato se globale)
  • string name; // costruttore chiamato, inizializzato con stringa vuota
  • string *pname; // puntatore non inizializzato (o azzerato se globale)
  • string &rname; // errore di compilazione se non si riesce a inizializzare questo
  • const string &crname; // errore di compilazione se non si riesce a inizializzare questo
  • int age; // valore scalare, non inizializzato e casuale (o azzerato se globale)

Ho sperimentato e sembra che string namesia vuoto dopo aver inizializzato la classe nello stack. Sei assolutamente sicuro della tua risposta?
bodacydo,

1
stringa avrà un costruttore che ha fornito una stringa vuota di default - Chiarirò la mia risposta
Paul Dixon

@bodacydo: Paul ha ragione, ma se ti interessa questo comportamento, non fa mai male essere esplicito. Lanciarlo nell'elenco di inizializzatori.
Stephen,

Grazie per chiarire e spiegare!
bodacydo,

2
Non è casuale! Casuale è una parola troppo grande per questo! Se i membri scalari fossero casuali, non avremmo bisogno di altri generatori di numeri casuali. Immagina un programma che analizzi i "resti" dei dati - come i file non ripristinati in memoria - i dati sono tutt'altro che casuali. Non è nemmeno indefinito! Di solito è difficile da definire, perché di solito non sappiamo cosa fa la nostra macchina. Se quei "dati casuali" che hai appena cancellato sono l'unica immagine di tuo padre, tua madre potrebbe persino trovarlo offensivo se dici che è casuale ...
slyy2048

5

I membri non statici non inizializzati conterranno dati casuali. In realtà, avranno solo il valore della posizione di memoria a cui sono assegnati.

Naturalmente per i parametri degli oggetti (come string) il costruttore dell'oggetto potrebbe eseguire un'inizializzazione predefinita.

Nel tuo esempio:

int *ptr; // will point to a random memory location
string name; // empty string (due to string's default costructor)
string *pname; // will point to a random memory location
string &rname; // it would't compile
const string &crname; // it would't compile
int age; // random value

2

I membri con un costruttore avranno il loro costruttore predefinito chiamato per l'inizializzazione.

Non puoi dipendere dai contenuti degli altri tipi.


0

Se è nello stack, il contenuto dei membri non inizializzati che non hanno il proprio costruttore sarà casuale e indefinito. Anche se è globale, sarebbe una cattiva idea fare affidamento sul fatto che vengano azzerati. Se è nello stack o no, se un membro ha il suo costruttore, verrà chiamato per inizializzarlo.

Quindi, se hai stringa * pname, il puntatore conterrà spazzatura casuale. ma per il nome della stringa, verrà chiamato il costruttore predefinito per la stringa, dandoti una stringa vuota. Per le variabili del tipo di riferimento, non sono sicuro, ma probabilmente sarà un riferimento a un pezzo di memoria casuale.


0

Dipende da come è costruita la classe

Rispondere a questa domanda arriva a comprendere un'enorme affermazione sul case switch nello standard del linguaggio C ++, e su cui è difficile intuire per i semplici mortali.

Come semplice esempio di quanto siano difficili:

main.cpp

#include <cassert>

int main() {
    struct C { int i; };

    // This syntax is called "default initialization"
    C a;
    // i undefined

    // This syntax is called "value initialization"
    C b{};
    assert(b.i == 0);
}

Nell'inizializzazione predefinita inizieresti da: https://en.cppreference.com/w/cpp/language/default_initialization andremo alla parte "Gli effetti dell'inizializzazione predefinita sono" e avvieremo l'istruzione case:

  • "se T è un non-POD ": no (la definizione di POD è di per sé un'enorme istruzione switch)
  • "se T è un tipo di array": no
  • "altrimenti, non viene fatto nulla": quindi viene lasciato con un valore indefinito

Quindi, se qualcuno decide di inizializzare un valore, andiamo su https://en.cppreference.com/w/cpp/language/value_initialization "Gli effetti dell'inizializzazione del valore sono" e avviamo l'istruzione case:

  • "se T è un tipo di classe senza costruttore predefinito o con un costruttore predefinito fornito dall'utente o eliminato": non è il caso. Passerai ora 20 minuti a cercare su Google questi termini:
    • abbiamo un costruttore predefinito implicitamente definito (in particolare perché non è stato definito nessun altro costruttore)
    • non è fornito dall'utente (implicitamente definito)
    • non è cancellato ( = delete)
  • "se T è un tipo di classe con un costruttore predefinito che non è né fornito né cancellato dall'utente": sì
    • "l'oggetto è inizializzato a zero e quindi è inizializzato in modo predefinito se ha un costruttore predefinito non banale": nessun costruttore non banale, solo a zero. La definizione di "zero-inizializza" almeno è semplice e fa quello che ti aspetti: https://en.cppreference.com/w/cpp/language/zero_initialization

Questo è il motivo per cui consiglio vivamente di non fare mai affidamento sull'inizializzazione zero "implicita". A meno che non ci siano forti motivi di prestazione, inizializza esplicitamente tutto, sia sul costruttore se ne hai definito uno, sia usando l'inizializzazione aggregata. Altrimenti rendi le cose molto rischiose per i futuri sviluppatori.

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.