2009-10-15 2 views
37

Lassen Sie uns ein einfaches Beispiel an:Warum arbeitet der Operator = an Strukturen, ohne dass er definiert wurde?

struct some_struct { 
    std::string str; 
    int a, b, c; 
} 

some_struct abc, abc_copy; 
abc.str = "some text"; 
abc.a = 1; 
abc.b = 2; 
abc.c = 3; 

abc_copy = abc; 

Dann ist abc_copy eine exakte Kopie von abc .. wie ist es möglich, ohneder Operator = definieren?

(Das hat mich überrascht, wenn sie auf einigen Code arbeiten ..)

Antwort

67

Wenn Sie nicht über diese vier Methoden (sechs in C++ 11) der Compiler definieren sie für Sie generieren:

  • Standardkonstruktor
  • Copykonstruktor
  • Zuweisungsoperator
  • Destructor
  • Verschieben Konstruktor (C++ 11)
  • Verschieben Zuordnung (C++ 11)

Wenn Sie wissen wollen, warum?
Es ist Abwärtskompatibilität mit C (weil C-Strukturen sind kopierbar mit = und in der Deklaration). Aber es erleichtert auch das Schreiben einfacher Klassen. Einige würden argumentieren, dass es Probleme wegen des "seichten Kopierproblems" hinzufügt. Mein Argument dagegen ist, dass Sie keine Klasse mit eigenen RAW-Zeigern haben sollten. Durch Verwendung der entsprechenden Smart Pointer geht dieses Problem weg.

Standard-Konstruktor (Wenn keine anderen Konstrukteuren definiert)

Der Compiler erzeugt Standardkonstruktors die Basisklassen Standardkonstruktors rufen und dann Standardkonstruktors jedes Mitglieder (in der Reihenfolge, wie sie deklariert sind)

destructor (Wenn kein destructor definiert)

Ruft die destructor jedes Mitglieds in umgekehrter Reihenfolge der Deklaration. Ruft dann den Destruktor der Basisklasse auf.

Copy-Konstruktor (Wenn kein Kopierkonstruktor definiert ist)

Ruft die Copykonstruktor Basisklasse Leiten des src-Objekt. Ruft dann den Kopierkonstruktor jedes Elements auf, indem er die src-Objektelemente als den zu kopierenden Wert verwendet.

Zuweisungsoperator

Ruft die Zuweisungsoperator Basisklasse Leiten des src-Objekt. Dann ruft der Zuweisungsoperator jedes Mitglied auf, wobei das src-Objekt als zu kopierender Wert verwendet wird.

verschieben Konstruktor (Wenn keine Bewegung Konstruktor definiert ist)

Ruft die Bewegung Konstruktor Basisklasse Leiten des src-Objekt. Ruft dann den Verschiebungskonstruktor jedes Elements auf, wobei die src-Objektelemente als zu verschiebender Wert verwendet werden.

Verschieben Zuweisungsoperator

Ruft die Basisklasse bewegen Zuweisungsoperator das Objekt src vorbei. Dann ruft der Verschiebezuweisungsoperator für jedes Mitglied das src-Objekt als den zu kopierenden Wert auf.

Wenn Sie definieren eine Klasse wie folgt:

struct some_struct: public some_base 
{ 
    std::string str1; 
    int a; 
    float b; 
    char* c; 
    std::string str2; 
}; 

Was der Compiler bauen ist:

struct some_struct: public some_base 
{ 
    std::string str1; 
    int a; 
    float b; 
    char* c; 
    std::string str2; 

    // Conceptually two different versions of the default constructor are built 
    // One is for value-initialization the other for zero-initialization 
    // The one used depends on how the object is declared. 
    //  some_struct* a = new some_struct;  // value-initialized 
    //  some_struct* b = new some_struct(); // zero-initialized 
    //  some_struct c;      // value-initialized 
    //  some_struct d = some_struct();  // zero-initialized 
    // Note: Just because there are conceptually two constructors does not mean 
    //  there are actually two built. 

    // value-initialize version 
    some_struct() 
     : some_base()   // value-initialize base (if compiler generated) 
     , str1()     // has a normal constructor so just call it 
     // PODS not initialized 
     , str2() 
    {} 

    // zero-initialize version 
    some_struct() 
     : some_base()   // zero-initialize base (if compiler generated) 
     , str1()     // has a normal constructor so just call it. 
     , a(0) 
     , b(0) 
     , c(0) // 0 is NULL 
     , str2() 
     // Initialize all padding to zero 
    {} 

    some_struct(some_struct const& copy) 
     : some_base(copy) 
     , str1(copy.str1) 
     , a(copy.a) 
     , b(copy.b) 
     , c(copy.c) 
     , str2(copy.str2) 
    {} 

    some_struct& operator=(some_struct const& copy) 
    { 
     some_base::operator=(copy); 
     str1 = copy.str1; 
     a = copy.a; 
     b = copy.b; 
     c = copy.c; 
     str2 = copy.str2; 
     return *this; 
    } 

    ~some_struct() 
    {} 
    // Note the below is pseudo code 
    // Also note member destruction happens after user code. 
    // In the compiler generated version the user code is empty 
     : ~str2() 
     // PODs don't have destructor 
     , ~str1() 
     , ~some_base(); 
    // End of destructor here. 

    // In C++11 we also have Move constructor and move assignment. 
    some_struct(some_struct&& copy) 
        // ^^^^ Notice the double && 
     : some_base(std::move(copy)) 
     , str1(std::move(copy.str1)) 
     , a(std::move(copy.a)) 
     , b(std::move(copy.b)) 
     , c(std::move(copy.c)) 
     , str2(std::move(copy.str2)) 
    {} 

    some_struct& operator=(some_struct&& copy) 
           // ^^^^ Notice the double && 
    { 
     some_base::operator=(std::move(copy)); 
     str1 = std::move(copy.str1); 
     a = std::move(copy.a); 
     b = std::move(copy.b); 
     c = std::move(copy.c); 
     str2 = std::move(copy.str2); 
     return *this; 
    } 
}; 
+0

Das ist schon eine wahnsinnig gute Antwort, aber ich würde gerne ein Beispiel mit den Smartpointern sehen. Ich war noch nie so erstaunlich bei auto_ptr – Hamy

+0

@Hamy: Dies ist die Informationen, die Sie benötigen, um den Smart Pointer zu bauen. Wenn Sie intelligente Zeiger verwenden, müssen Sie sich darüber nicht wirklich Gedanken machen. Sie müssen sich nur um das Obige kümmern, wenn Sie RAW-eigene Zeiger in Ihrer Klasse haben. –

+1

Diese Antwort verwirrt die Arten von [Initialisierung] (http://en.cppreference.com/w/cpp/language/initialization). Ohne Initialisierer wird die Struktur [* default initialized *] (http://en.cppreference.com/w/cpp/language/default_initialization): ihre POD-typisierten Mitglieder werden unbestimmte Werte annehmen. Mit einem leeren Initialisierer wird die Struktur [* value initialized *] (http://en.cppreference.com/w/cpp/language/value_initialization): ihre POD-typisierten Mitglieder werden [* null initialisiert *] (http : //en.cppreference.com/w/cpp/language/zero_initialization). –

2

Der Compiler einige Mitglieder für Sie synthetisieren, wenn man sie nicht explizit selbst definieren. Der Zuweisungsoperator ist einer von ihnen. Ein Kopierkonstruktor ist ein anderer, und Sie erhalten auch einen Destruktor. Sie erhalten auch einen Standardkonstruktor, wenn Sie keine eigenen Konstruktoren bereitstellen. Darüber hinaus bin ich mir nicht sicher, was sonst noch, aber ich glaube, dass es andere geben könnte (der Link in der Antwort von 280Z28 schlägt jedoch etwas anderes vor und ich kann mich nicht erinnern, wo ich es jetzt gelesen habe, vielleicht sind es nur vier).

0

Strukturen sind im Grunde eine Verkettung seiner Komponenten im Speicher (mit einigen möglichen Polsterung für die Ausrichtung eingebaut). Wenn Sie einer Struktur den Wert einer anderen zuweisen, werden die Werte gerade übernommen.

4

Aber es ist definiert. Im Standard. Wenn Sie keinen Operator = angeben, wird Ihnen einer bereitgestellt. Und der Standardoperator kopiert nur jede der Mitgliedsvariablen. Und woher weiß es, wie man jedes Mitglied kopiert? es ruft ihren Operator = auf (was, wenn nicht definiert, standardmäßig geliefert wird ...).

4

Dieses Verhalten ist notwendig, um die Quelle der Kompatibilität mit C.

C Ihnen nicht zu erhalten die Möglichkeit geben,/Überschreibung Operatoren zu definieren, so structs normalerweise mit dem Operator = kopiert werden.

+0

K & R C war es nicht für Strukturen mit '=' an kopiert werden Alles, und ich bin mir nicht sicher über C89. Wenn es in C99 eingeführt wurde, dann würde ich argumentieren, dass es aufgrund von C++ Einfluss war. – ephemient

+1

Laut K & R (2. Auflage, 1988, S. 127) wurde es von ANSI C eingeführt, aber die meisten existierenden Compiler unterstützten es bereits. – Ferruccio

3

Der Zuweisungsoperator (operator=) ist eine der implizit generierten Funktionen für eine Struktur oder Klasse in C++.

Hier ist ein Bezug der Beschreibung der 4 implizit erzeugt Mitglieder:
http://www.cs.ucf.edu/~leavens/larchc++manual/lcpp_136.html

Kurz gesagt, die implizit erzeugt Element führt eine memberwise shallow copy. Hier ist die lange Version von der verknüpften Seite:

Die implizit generierte Zuweisungsoperatorspezifikation, falls erforderlich, ist die folgende. Die Spezifikation besagt, dass das Ergebnis das Objekt ist, das zugewiesen wird (self), und dass der Wert des abstrakten Werts self im Post-Status self "gleich dem Wert des abstrakten Werts des Arguments from ist.

// @(#)$Id: default_assignment_op.lh,v 1.3 1998/08/27 22:42:13 leavens Exp $ 
#include "default_interfaces.lh" 

T& T::operator = (const T& from) throw(); 
//@ behavior { 
//@ requires assigned(from, any) /\ assigned(from\any, any); 
//@ modifies self; 
//@ ensures result = self /\ self" = from\any\any; 
//@ ensures redundantly assigned(self, post) /\ assigned(self', post); 
//   thus 
//@ ensures redundantly assigned(result, post) /\ assigned(result', post); 
//@ } 
+1

Ist die Spezifikation der leeren Ausnahme korrekt? –

+0

Der Standardzuweisungsoperator kann nicht ausgelöst werden, da er keinen Speicher reserviert. : dunno: –

+3

@Rob: Die Definition des Standardkopiezuweisungsoperators ab 12.8: 10 erwähnt keine throw-Klausel. Das macht für mich Sinn, da ein Standardkopiezuweisungsoperator eine nicht standardmäßige Zuweisung aufrufen kann, die erwerfen könnte. In dem spezifischen Beispiel, das in der Frage gegeben wird, kann offensichtlich "std :: string :: operator = (const std :: string &)" werfen. –

7

In C++ sind structs entspricht Klassen, in denen Mitglieder standardmäßig öffentlich statt privaten Zugang

C++ Compiler auch folgende spezielle Mitglieder einer Klasse erzeugen automatisch, wenn sie nicht vorhanden sind.

  • Standardkonstruktor - keine Argumente, Standard initialisiert alles.
  • Kopieren Konstruktor - dh eine Methode mit dem gleichen Namen wie die Klasse, die einen Verweis auf ein anderes Objekt der gleichen Klasse nimmt. Kopiert alle Werte über.
  • Destruktor - Wird aufgerufen, wenn das Objekt zerstört wird. Standardmäßig tut nichts.
  • Zuweisungsoperator - Wird aufgerufen, wenn eine Struktur/Klasse einer anderen zugewiesen ist. Dies ist die automatisch generierte Methode, die im obigen Fall aufgerufen wird.
+1

Ein impliziter Standardkonstruktor wird auch nicht zur Verfügung gestellt, wenn * irgendein * benutzerdefinierter Konstruktor vorhanden ist. – sellibitze

+0

Ein impliziter Destruktor ruft auch Destruktoren von Mitgliedern und Unterobjekten auf (wenn es welche gibt) – sellibitze

Verwandte Themen