C ++ 11 consente l'inizializzazione in classe di membri non statici e non const. Cosa è cambiato?


89

Prima di C ++ 11, potevamo eseguire solo l'inizializzazione in classe su membri const statici di tipo integrale o enumerazione. Stroustrup ne discute nelle sue domande frequenti su C ++ , fornendo il seguente esempio:

class Y {
  const int c3 = 7;           // error: not static
  static int c4 = 7;          // error: not const
  static const float c5 = 7;  // error: not integral
};

E il seguente ragionamento:

Allora perché esistono queste restrizioni scomode? Una classe è tipicamente dichiarata in un file di intestazione e un file di intestazione è tipicamente incluso in molte unità di traduzione. Tuttavia, per evitare complicate regole del linker, C ++ richiede che ogni oggetto abbia una definizione univoca. Questa regola sarebbe infranta se C ++ consentisse la definizione in classe di entità che dovevano essere archiviate in memoria come oggetti.

Tuttavia, C ++ 11 attenua queste restrizioni, consentendo l'inizializzazione in classe di membri non statici (§12.6.2 / 8):

In un costruttore non delegante, se un dato membro di dati non statici o una classe base non è designato da un mem-initializer-id (incluso il caso in cui non esiste un mem-initializer-list perché il costruttore non ha ctor-initializer ) e l'entità non è una classe base virtuale di una classe astratta (10.4), quindi

  • se l'entità è un membro dati non statico che ha un inizializzatore di parentesi graffa o uguale , l'entità viene inizializzata come specificato in 8.5;
  • altrimenti, se l'entità è un membro variante (9.5), non viene eseguita alcuna inizializzazione;
  • altrimenti, l'entità è inizializzata per default (8.5).

La sezione 9.4.2 consente anche l'inizializzazione in classe di membri statici non const se sono contrassegnati con lo constexprspecificatore.

Quindi cosa è successo alle ragioni delle restrizioni che avevamo in C ++ 03? Accettiamo semplicemente le "regole complicate del linker" o è cambiato qualcos'altro che lo rende più facile da implementare?


5
Non è successo niente. I compilatori sono diventati più intelligenti con tutti questi modelli di sola intestazione, quindi l'estensione è relativamente facile ora.
Öö Tiib

È interessante notare che sul mio IDE quando seleziono la compilation pre C ++ 11 mi è permesso inizializzare membri integrali const non statici
Dean P

Risposte:


67

La risposta breve è che hanno mantenuto il linker più o meno lo stesso, a scapito di rendere il compilatore ancora più complicato di prima.

Cioè, invece di ciò che si traduce in più definizioni per il linker da risolvere, si ottiene ancora una sola definizione e il compilatore deve risolverla.

Porta anche a regole un po 'più complesse che il programmatore deve tenere in ordine, ma è per lo più abbastanza semplice da non essere un grosso problema. Le regole extra entrano in gioco quando hai due diversi inizializzatori specificati per un singolo membro:

class X { 
    int a = 1234;
public:
    X() = default;
    X(int z) : a(z) {}
};

Ora, le regole extra a questo punto riguardano il valore utilizzato per inizializzare aquando si utilizza il costruttore non predefinito. La risposta è abbastanza semplice: se usi un costruttore che non specifica nessun altro valore, allora 1234verrebbe usato per inizializzare a- ma se usi un costruttore che specifica qualche altro valore, allora il1234 viene sostanzialmente ignorato.

Per esempio:

#include <iostream>

class X { 
    int a = 1234;
public:
    X() = default;
    X(int z) : a(z) {}

    friend std::ostream &operator<<(std::ostream &os, X const &x) { 
        return os << x.a;
    }
};

int main() { 
    X x;
    X y{5678};

    std::cout << x << "\n" << y;
    return 0;
}

Risultato:

1234
5678

1
Sembra che questo fosse del tutto possibile prima. Ha solo reso più difficile il lavoro di scrittura di un compilatore. È una dichiarazione corretta?
allyourcode

10
@allyourcode: Sì e no. Sì, ha reso più difficile la scrittura del compilatore. Ma no, perché ha anche reso la scrittura della specifica C ++ un po 'più difficile.
Jerry Coffin

C'è una differenza nel modo in cui inizializzare il membro della classe: int x = 7; o int x {7} ;?
mbaros

9

Immagino che il ragionamento potrebbe essere stato scritto prima che i modelli fossero finalizzati. Dopo che tutte le "complicate regole del linker" necessarie per gli inizializzatori in classe dei membri statici erano / erano già necessarie affinché C ++ 11 supportasse i membri statici dei modelli.

Ritenere

struct A { static int s = ::ComputeSomething(); }; // NOTE: This isn't even allowed,
                                                   // thanks @Kapil for pointing that out

// vs.

template <class T>
struct B { static int s; }

template <class T>
int B<T>::s = ::ComputeSomething();

// or

template <class T>
void Foo()
{
    static int s = ::ComputeSomething();
    s++;
    std::cout << s << "\n";
}

Il problema per il compilatore è lo stesso in tutti e tre i casi: in quale unità di traduzione dovrebbe emettere la definizione se il codice necessario per inizializzarlo? La soluzione semplice è emetterlo ovunque e lasciare che il linker lo risolva. Ecco perché i linker già supportavano cose come __declspec(selectany). Non sarebbe stato possibile implementare C ++ 03 senza di esso. Ed è per questo che non è stato necessario estendere il linker.

Per dirla in modo più schietto: penso che il ragionamento dato nel vecchio standard sia semplicemente sbagliato.


AGGIORNARE

Come ha sottolineato Kapil, il mio primo esempio non è nemmeno consentito nello standard corrente (C ++ 14). L'ho lasciato comunque, perché IMO è il caso più difficile per l'implementazione (compilatore, linker). Il punto è: anche questo caso non è più difficile di quello che è già consentito, ad esempio quando si usano i modelli.


Peccato che questo non abbia ottenuto alcun voto positivo, poiché molte delle funzionalità di C ++ 11 sono simili in quanto i compilatori includevano già le funzionalità o le ottimizzazioni necessarie.
Alex Court,

@AlexCourt Ho scritto questa risposta di recente. La domanda e la risposta di Jerry sono però del 2012. Quindi immagino sia per questo che la mia risposta non ha ricevuto molta attenzione.
Paul Groke,

1
Questo non complie "struct A {static int s = :: ComputeSomething ();}" perché solo static const può essere inizializzato in classe
PapaDiHatti

8

In teoria la So why do these inconvenient restrictions exist?...ragione è valida ma può piuttosto essere facilmente aggirata e questo è esattamente ciò che fa C ++ 11.

Quando includi un file, include semplicemente il file e ignora qualsiasi inizializzazione. I membri vengono inizializzati solo quando si crea un'istanza della classe.

In altre parole, l'inizializzazione è ancora legata al costruttore, solo la notazione è diversa ed è più conveniente. Se il costruttore non viene chiamato, i valori non vengono inizializzati.

Se viene chiamato il costruttore, i valori vengono inizializzati con l'inizializzazione in classe se presente o il costruttore può sovrascriverlo con la propria inizializzazione. Il percorso di inizializzazione è essenzialmente lo stesso, cioè tramite il costruttore.

Questo è evidente dalle FAQ di Stroustrup su C ++ 11.


Ri "Se il costruttore non viene chiamato, i valori non vengono inizializzati": Come posso aggirare l'inizializzazione del membro Y::c3nella domanda? A quanto ho capito, c3verrà sempre inizializzato a meno che non ci sia un costruttore che sovrascrive il valore predefinito fornito nella dichiarazione.
Peter - Ripristina Monica il
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.