2010-05-14 16 views
28

Als Nachtrag zu this question, was hier vor sich geht:initialisieren structs in C++

#include <string> 
using namespace std; 

struct A { 
    string s; 
}; 

int main() { 
    A a = {0}; 
} 

Offensichtlich kann man nicht eine std :: string auf Null gesetzt. Kann jemand eine Erklärung geben (bitte mit Referenzen auf den C++ Standard), was hier eigentlich passieren soll? Und dann zum Beispiel erklären):

int main() { 
    A a = {42}; 
} 

Sind beide von diesen gut definiert?

Wieder einmal eine peinliche Frage für mich - ich immer meinen structs Bauer geben, so dass die Frage noch nie aufgetreten ist.

+1

Die Klassenschablone 'boost :: array' ist ebenfalls ein Aggregat. So können Sie zum Beispiel 'array a = {" foo "," bar "};' damit machen. Mein Lazy-Construct-Array ist auch ein Aggregat: http://stackoverflow.com/questions/2662417/c-suppress-automatic-initialization-and-destruction/2662526#2662526 –

+3

Implizite Konvertierungen + Aggregate ... ಠ_ಠ –

+0

@litb als ich das erste Feature von 'boost :: array' sah, hatte ich eine Erleuchtung, AKA sexuelle Befriedigung-des-Gehirns. Einfache Dinge, die so viel Sinn machen, tendieren dazu, mir das zu antun. – wilhelmtell

Antwort

29

Ihre Struktur ist ein Aggregat, so dass die normalen Regeln für die aggregierte Initialisierungsarbeit dafür. Der Prozess ist in 8.5.1 beschrieben. Grundsätzlich ist das ganze 8.5.1 diesem Thema gewidmet, daher sehe ich keinen Grund, das Ganze hier zu kopieren. Die allgemeine Idee ist praktisch das gleiche es in C war, nur um C++ angepasst: Sie nehmen einen Initialisierer von rechts, nehmen Sie ein Mitglied aus der linken Seite und Sie initialisieren das Element mit dem Initialisierer. Nach 8,5/12, so wird dies eine copy-Initialisierung sein.

Wenn Sie das tun

A a = { 0 }; 

Sie sind grundsätzlich a.s mit 0-Kopie zu initialisieren, dh für a.s es

string s = 0; 

Die obigen compiles semantisch äquivalent ist, weil std::string von einem const char * Zeiger umwandelbar ist . (Und es ist nicht definiertes Verhalten, da Null-Zeiger nicht ein gültiges Argument ist in diesem Fall.)

Ihre 42 Version wird aus dem gleichen Grunde nicht kompiliert die

string s = 42; 

wird nicht kompiliert. 42 ist kein Null-Zeiger-Konstante und std::string hat keine Mittel für die Umwandlung von int Typ.

P.S. Nur für den Fall: beachten Sie, dass die Definition von aggregierte in C++ nicht rekursiv ist (wie beispielsweise die Definition von POD, entgegengesetzt). std::string ist kein Aggregat, aber es ändert nichts für Ihre A. A ist immer noch ein Aggregat.

+0

§12.6.1 ist auch relevant, wie in § 8.5.1 13. – outis

+0

@outis: Ich 12.6.1 durchgesehen und ich konnte nicht sofort sehen, was es hinzugefügt, was bereits in 8.5 war. Jedes Mal, wenn 12.6.1 sich mit der Aggregat-Initialisierung beschäftigt, scheint es sich auf 8.5 zu beziehen :) – AnT

+0

Es ist interessant zu sehen, dass in 'basic_string (Größe_Typ n, charT c, const Allocator a = Allocator())' es einen Grund gibt, warum 'size_type n' hat keinen Standardwert. Der Grund ist, dass es eine schlechte Idee ist, einen Zeiger und eine Ganzzahl zu überladen. Der Wert 0 (Null) ist streng genommen eine Ganzzahl, kein Zeiger, und so wäre es nicht möglich, über die Pointer-Überladung mit einem Nullzeiger zu konstruieren, es sei denn natürlich, Sie würden dies explizit tun.Der Standard vermeidet diese Verwirrung, indem er einen Zeichentyp erfordert, wenn Sie bei der String-Konstruktion eine String-Länge angeben. – wilhelmtell

8

8.5.1/12 „Aggregate“, sagen:

Alle impliziten Typkonvertierungen (Ziffer 4) in Betracht gezogen werden, wenn sie mit einem Initialisierer von einer Initialisierer-Liste, um das Aggregat Mitglied initialisiert.

So

A a = {0}; 

wird mit einem NULL initialisiert, char* (wie AndreyT und Johannes angegeben) und

A a = {42}; 

wird bei der Kompilierung fehlschlagen, da gibt es keine implizite Konvertierung thatll mit einem std::string Konstruktor übereinstimmen.

2

Wie bereits erwähnt, "funktioniert" das, weil String einen Konstruktor hat, der 0 als Parameter annehmen kann. Wenn wir sagen:

#include <map> 
using namespace std; 

struct A { 
    map <int,int> m; 
}; 

int main() { 
    A a = {0}; 
} 

dann erhalten wir einen Übersetzungsfehler, da die Karte Klasse verfügt nicht über einen solchen Konstruktor.

+0

Warum wird Ihr Bild-Avatar in dieser Antwort nicht angezeigt? Ist es auf Community-Antworten versteckt? –

+0

@Johannes Ein Geheimnis! Willst du es als Fehler auf Meta melden oder soll ich? –

+0

@Neil gehen Sie dafür :) –

1

In 21.3.1/9 verbietet der Standard, dass das char* Argument des relevanten Konstruktors eines std::basic_string ein Nullzeiger ist. Dies sollte eine std::logic_error werfen, aber ich habe noch zu sehen, wo in der Norm ist die Garantie, dass die Verletzung einer Vorbedingung eine std::logic_error wirft.

+0

Wenn ich mich nicht irre, garantiert die Verletzung einer Vorbedingung undefiniertes Verhalten, keine Ausnahme. –

+1

@James g ++ 4.0.1 auf OS X 10.5.8 wirft zur Konstruktionszeit einen 'std :: logic_error'. 19.1.1 sagt, das ist, wofür 'logic_error' gedacht ist, aber ich kann keine Garantie dafür finden, was passiert, wenn eine Invariante oder Vorbedingung verletzt wird. – wilhelmtell

3

0 ist ein Nullzeiger Konstante

S.4.9:

Ein Nullzeiger Konstante ist ein integraler konstanter Ausdruck (5.19) R-Wert von Integer-Typ, die auf Null auswertet.

Ein Nullzeiger Konstante kann zu jedem anderen Zeigertyp umgewandelt werden:

S.4.9:

A null konstante Zeiger kann auf einen Zeigertyp umgewandelt werden; das Ergebnis ist der Null-Zeiger-Wert dieser Typ

Was Sie für die Definition von A gab ein Aggregat betrachtet:

S.8.5.1:

Ein Aggregat ist ein Array oder eine Klasse ohne von Benutzern deklarierte Konstruktoren, keine privaten oder geschützten nicht statische Datenelemente, keine Basisklassen und keine virtuellen Funktionen.

Du Angabe einer Initialisierer-Klausel:

S.8.5.1:

Wenn ein Aggregat der Initialisierer initialisiert wird von einer Klammer einen Initialisierer-Klausel enthalten kann, die aus geschlossenen, durch Kommata getrennte Liste von Initialisierer-Klauseln für die Mitglieder des Aggregats

A enthält ein Mitglied des Aggregats vom Typ std::string, und die Initialisierungsklausel gilt dafür.

Ihr Aggregat ist kopier initialisierten

Wenn ein Aggregat (ob Klassen- oder Array) Mitglieder des Klassentypen enthält und durch eine geschweifte Klammer eingeschlossen Initialisierer-Liste initialisiert, wobei jedes solches Element Exemplar -initialisiert.

Kopieren Mittel initialisiert, die Ihnen entspricht std::string s = 0 oder std::string s = 42 haben;

S.8.5-12

Die Initialisierung, die in Argumentübergabe, Funktionsrückgabe auftritt, eine Ausnahme (15.1) zu werfen, Handhabung eine Ausnahme (15.3) und in geschweiften Klammern stehenden Initialisierer Listen (8,5 .1) heißt Kopierinitialisierung und entspricht der Form T x = a;

std::string s = 42 kompiliert nicht, weil es keine implizite Konvertierung ist, std::string s = 0 kompiliert (weil eine implizite Konvertierung vorhanden ist), führt aber zu undefinierten Verhalten.

std::string ‚s Konstruktor für const char* nicht als explicit definiert, was bedeutet, Sie können dies tun: std::string s = 0

Nur um zu zeigen, dass die Dinge tatsächlich Kopie initialisiert ist, könnten Sie diesen einfachen Test machen:

class mystring 
{ 
public: 

    explicit mystring(const char* p){} 
}; 

struct A { 
    mystring s; 
}; 


int main() 
{ 
    //Won't compile because no implicit conversion exists from const char* 
    //But simply take off explicit above and everything compiles fine. 
    A a = {0}; 
    return 0; 
}