C ++ 11 ermöglicht die In-Class-Initialisierung von nicht statischen und nicht konstanten Elementen. Was hat sich geändert?

83

Vor C ++ 11 konnten wir nur eine In-Class-Initialisierung für statische const-Elemente vom Integral- oder Aufzählungstyp durchführen. Stroustrup erläutert dies in seinen C ++ - FAQ anhand des folgenden Beispiels:

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

Und die folgenden Überlegungen:

Warum gibt es diese unbequemen Einschränkungen? Eine Klasse wird normalerweise in einer Header-Datei deklariert, und eine Header-Datei ist normalerweise in vielen Übersetzungseinheiten enthalten. Um jedoch komplizierte Linkerregeln zu vermeiden, erfordert C ++, dass jedes Objekt eine eindeutige Definition hat. Diese Regel würde verletzt, wenn C ++ die Definition von Entitäten in der Klasse zulässt, die als Objekte im Speicher gespeichert werden müssen.

In C ++ 11 werden diese Einschränkungen jedoch gelockert, sodass nicht statische Elemente in der Klasse initialisiert werden können (§12.6.2 / 8):

Wenn in einem nicht delegierenden Konstruktor ein bestimmtes nicht statisches Datenelement oder eine Basisklasse nicht durch eine mem-initializer-id gekennzeichnet ist (einschließlich des Falls, in dem keine mem-initializer-Liste vorhanden ist, weil der Konstruktor keinen ctor-initializer hat ) und die Entität ist also keine virtuelle Basisklasse einer abstrakten Klasse (10.4)

  • Wenn die Entität ein nicht statisches Datenelement ist, das einen Klammer-oder-Gleich-Initialisierer hat , wird die Entität wie in 8.5 angegeben initialisiert.
  • Andernfalls wird keine Initialisierung durchgeführt, wenn die Entität ein Variantenmitglied ist (9.5).
  • Andernfalls wird die Entität standardmäßig initialisiert (8.5).

Abschnitt 9.4.2 ermöglicht auch die In-Class-Initialisierung von nicht konstanten statischen Elementen, wenn diese mit dem Bezeichner markiert sind constexpr.

Was ist nun mit den Gründen für die Einschränkungen in C ++ 03 passiert? Akzeptieren wir einfach die "komplizierten Linker-Regeln" oder hat sich etwas anderes geändert, das die Implementierung erleichtert?

Joseph Mansfield
quelle
5
Nichts ist passiert. Compiler sind mit all diesen Nur-Header-Vorlagen intelligenter geworden, sodass die Erweiterung jetzt relativ einfach ist.
Öö Tiib
Interessanterweise darf ich auf meiner IDE, wenn ich die Kompilierung vor C ++ 11 auswähle, nicht statische konstante Integralelemente initialisieren
Dean P

Antworten:

66

Die kurze Antwort ist, dass sie den Linker ungefähr gleich gehalten haben, auf Kosten des Compilers, der noch komplizierter als zuvor ist.

Das heißt, anstatt dass dies zu mehreren Definitionen führt, die der Linker aussortieren muss, führt dies immer noch nur zu einer Definition, und der Compiler muss sie aussortieren.

Es führt auch zu etwas komplexeren Regeln, die der Programmierer ebenfalls aussortieren muss, aber es ist meistens einfach genug, dass es keine große Sache ist. Die zusätzlichen Regeln werden eingeführt, wenn für ein einzelnes Mitglied zwei verschiedene Initialisierer angegeben sind:

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

Die zusätzlichen Regeln an dieser Stelle befassen sich nun mit dem Wert, der zum Initialisieren verwendet wird, awenn Sie den nicht standardmäßigen Konstruktor verwenden. Die Antwort darauf ist ziemlich einfach: Wenn Sie einen Konstruktor verwenden, der keinen anderen Wert angibt 1234, wird der zum Initialisieren verwendet. aWenn Sie jedoch einen Konstruktor verwenden, der einen anderen Wert angibt, wird der Wert 1234grundsätzlich ignoriert.

Beispielsweise:

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

Ergebnis:

1234
5678
Jerry Sarg
quelle
1
Scheint so, als wäre das schon mal möglich gewesen. Es machte das Schreiben eines Compilers nur schwieriger. Ist das eine faire Aussage?
Allyourcode
9
@allyourcode: Ja und nein. Ja, es hat das Schreiben des Compilers erschwert. Aber nein, weil es auch das Schreiben der C ++ - Spezifikation etwas schwieriger gemacht hat.
Jerry Coffin
Gibt es einen Unterschied beim Initialisieren des Klassenmitglieds: int x = 7; oder int x {7};?
Mbaros
9

Ich denke, dass die Argumentation möglicherweise geschrieben wurde, bevor die Vorlagen fertiggestellt wurden. Schließlich waren / waren die "komplizierten Linker-Regeln", die für In-Class-Initialisierer statischer Elemente in der Klasse erforderlich waren, bereits erforderlich, damit C ++ 11 statische Elemente von Vorlagen unterstützt.

Erwägen

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

Das Problem für den Compiler ist in allen drei Fällen dasselbe: In welcher Übersetzungseinheit sollte er die Definition sund den Code ausgeben, die zur Initialisierung erforderlich sind? Die einfache Lösung besteht darin, es überall auszugeben und vom Linker aussortieren zu lassen. Deshalb haben die Linker bereits Dinge wie unterstützt __declspec(selectany). Ohne C ++ 03 wäre es einfach nicht möglich gewesen. Deshalb musste der Linker nicht erweitert werden.

Um es klarer auszudrücken: Ich denke, die Argumentation im alten Standard ist einfach falsch.


AKTUALISIEREN

Wie Kapil betonte, ist mein erstes Beispiel im aktuellen Standard (C ++ 14) nicht einmal erlaubt. Ich habe es trotzdem belassen, weil es IMO der schwierigste Fall für die Implementierung ist (Compiler, Linker). Mein Punkt ist: Auch dieser Fall ist nicht schwieriger als das, was bereits erlaubt ist, z. B. bei der Verwendung von Vorlagen.

Paul Groke
quelle
Schade, dass dies keine positiven Bewertungen erhalten hat, da viele der C ++ 11-Funktionen insofern ähnlich sind, als Compiler bereits die erforderlichen Funktionen oder Optimierungen enthalten haben.
Alex Court
@AlexCourt Ich habe diese Antwort kürzlich geschrieben. Die Frage und Jerrys Antwort stammen jedoch aus dem Jahr 2012. Ich denke, deshalb hat meine Antwort nicht viel Aufmerksamkeit erhalten.
Paul Groke
1
Dies entspricht nicht "struct A {static int s = :: ComputeSomething ();}", da nur statische const in der Klasse initialisiert werden kann
Kapil
7

Theoretisch ist der So why do these inconvenient restrictions exist?...Grund gültig, kann aber leicht umgangen werden, und genau das tut C ++ 11.

Wenn Sie umfassen eine Datei enthält sie einfach die Datei und missachtet jede Initialisierung. Die Mitglieder werden nur initialisiert, wenn Sie die Klasse instanziieren .

Mit anderen Worten, die Initialisierung ist immer noch an den Konstruktor gebunden, nur die Notation ist anders und bequemer. Wenn der Konstruktor nicht aufgerufen wird, werden die Werte nicht initialisiert.

Wenn der Konstruktor aufgerufen wird, werden die Werte mit einer In-Class-Initialisierung initialisiert, falls vorhanden, oder der Konstruktor kann diese mit einer eigenen Initialisierung überschreiben. Der Initialisierungspfad ist im Wesentlichen der gleiche, dh über den Konstruktor.

Dies geht aus Stroustrups eigenen FAQ zu C ++ 11 hervor.

zar
quelle
Betreff "Wenn der Konstruktor nicht aufgerufen wird, werden die Werte nicht initialisiert": Wie kann ich die Elementinitialisierung Y::c3in der Frage umgehen ? Soweit ich weiß, c3wird es immer initialisiert, es sei denn, es gibt einen Konstruktor, der den in der Deklaration angegebenen Standard überschreibt.
Peter - Reinstate Monica