Suche…


Virtuelle und geschützte Destruktoren

Eine Klasse, die entworfen wurde, um vererbt zu werden, wird als Basisklasse bezeichnet. Bei den speziellen Memberfunktionen einer solchen Klasse ist Vorsicht geboten.

Eine Klasse, die zur Laufzeit (durch einen Zeiger auf die Basisklasse) polymorph verwendet wird, sollte den Destruktor als virtual deklarieren. Dadurch können die abgeleiteten Teile des Objekts ordnungsgemäß zerstört werden, selbst wenn das Objekt durch einen Zeiger auf die Basisklasse zerstört wird.

class Base {
public:
    virtual ~Base() = default;

private:
    //    data members etc.
};

class Derived : public Base { //  models Is-A relationship
public:
    //    some methods

private:
    //    more data members
};

//    virtual destructor in Base ensures that derived destructors
//    are also called when the object is destroyed
std::unique_ptr<Base> base = std::make_unique<Derived>();
base = nullptr;  //    safe, doesn't leak Derived's members

Wenn die Klasse nicht polymorph sein muss, die Schnittstelle jedoch vererbt werden muss, verwenden Sie einen nicht virtuellen protected Destruktor.

class NonPolymorphicBase {
public:
    //    some methods

protected:
    ~NonPolymorphicBase() = default; //    note: non-virtual

private:
    //    etc.
};

Eine solche Klasse kann niemals durch einen Zeiger zerstört werden, um stille Undichtigkeiten durch Schneiden zu vermeiden.

Diese Technik gilt insbesondere für Klassen, die als private Basisklassen konzipiert wurden. Mit einer solchen Klasse können einige allgemeine Implementierungsdetails gekapselt werden, während virtual Methoden als Anpassungspunkte bereitgestellt werden. Diese Art von Klassen sollte niemals polymorph verwendet werden, und ein protected Destruktor hilft, diese Anforderung direkt im Code zu dokumentieren.

Schließlich können einige Klassen verlangen, dass sie niemals als Basisklasse verwendet werden. In diesem Fall kann die Klasse als final markiert werden. In diesem Fall ist ein normaler nicht virtueller öffentlicher Destruktor in Ordnung.

class FinalClass final {  //    marked final here
public:
    ~FinalClass() = default;

private:
    //    etc.
};

Implizites Verschieben und Kopieren

Beachten Sie, dass das Deklarieren eines Destruktors den Compiler daran hindert, implizite Verschiebungskonstruktoren und Verschiebungszuweisungsoperatoren zu generieren. Wenn Sie einen Destruktor deklarieren, müssen Sie auch die entsprechenden Definitionen für die Verschiebungsoperationen hinzufügen.

Durch das Deklarieren von Verschiebungsvorgängen wird außerdem die Erzeugung von Kopiervorgängen unterdrückt. Diese sollten ebenfalls hinzugefügt werden (wenn die Objekte dieser Klasse Kopiersemantik benötigen).

class Movable {
public:
    virtual ~Movable() noexcept = default;

    //    compiler won't generate these unless we tell it to
    //    because we declared a destructor
    Movable(Movable&&) noexcept = default;
    Movable& operator=(Movable&&) noexcept = default;

    //    declaring move operations will suppress generation
    //    of copy operations unless we explicitly re-enable them
    Movable(const Movable&) = default;
    Movable& operator=(const Movable&) = default;
};

Kopieren und tauschen

Wenn Sie eine Klasse schreiben, die Ressourcen verwaltet, müssen Sie alle speziellen Member-Funktionen implementieren (siehe Drei- / Fünf-Regel / Null ). Der direkteste Ansatz zum Schreiben des Kopierkonstruktors und des Zuweisungsoperators wäre:

person(const person &other)
    : name(new char[std::strlen(other.name) + 1])
    , age(other.age)
{
    std::strcpy(name, other.name);
}

person& operator=(person const& rhs) {
    if (this != &other) {
        delete [] name;
        name = new char[std::strlen(other.name) + 1];
        std::strcpy(name, other.name);
        age = other.age;
    }

    return *this;
}

Dieser Ansatz hat jedoch einige Probleme. Die starke Ausnahmegarantie fällt nicht aus - wenn new[] wirft, haben wir die Ressourcen, die sich im Besitz this befinden, bereits gelöscht und können sie nicht wiederherstellen. Wir duplizieren viel von der Logik der Kopierkonstruktion bei der Kopierzuweisung. Und wir müssen uns an die Selbstzuweisungsprüfung erinnern, die den Kopiervorgang normalerweise nur belastet, aber immer noch kritisch ist.

Um die starke Ausnahmegarantie zu erfüllen und die Codevervielfältigung zu vermeiden (doppelt so mit dem nachfolgenden Verschiebungszuweisungsoperator), können wir das Copy-and-Swap-Idiom verwenden:

class person {
    char* name;
    int age;
public:
    /* all the other functions ... */

    friend void swap(person& lhs, person& rhs) {
        using std::swap; // enable ADL

        swap(lhs.name, rhs.name);
        swap(lhs.age, rhs.age);
    }

    person& operator=(person rhs) {
        swap(*this, rhs);
        return *this;
    }
};

Warum funktioniert das? Überlegen Sie, was passiert, wenn wir haben

person p1 = ...;
person p2 = ...;
p1 = p2;

Zuerst kopieren wir rhs aus p2 (was wir hier nicht duplizieren mussten). Wenn diese Operation wirft, machen wir nichts in operator= und p1 bleibt unangetastet. Als nächstes tauschen wir die Mitglieder zwischen *this und rhs , und dann geht rhs außer Reichweite. Wenn operator= , reinigt , dass implizit die ursprünglichen Ressourcen this (über den destructor, die wir nicht duplizieren müssen). Selbstzuweisung funktioniert auch - mit Copy-and-Swap ist dies weniger effizient (erfordert eine zusätzliche Zuweisung und Freigabe), aber in diesem unwahrscheinlichen Fall verlangsamen wir den typischen Anwendungsfall nicht, um dies zu berücksichtigen.

C ++ 11

Die obige Formulierung funktioniert bereits für die Bewegungszuweisung.

p1 = std::move(p2);

Hier verschieben wir rhs von p2 , und der Rest ist genauso gültig. Wenn eine Klasse verschiebbar, aber nicht kopierbar ist, muss die Kopierzuweisung nicht gelöscht werden, da dieser Zuweisungsoperator aufgrund des gelöschten Kopierkonstruktors einfach falsch formatiert wird.

Standardkonstruktor

Ein Standardkonstruktor ist ein Typ eines Konstruktors, der beim Aufruf keine Parameter erfordert. Es ist nach dem von ihm konstruierten Typ benannt und ist eine Memberfunktion (wie alle Konstruktoren).

class C{
    int i;
public:
    // the default constructor definition
    C()
    : i(0){ // member initializer list -- initialize i to 0
        // constructor function body -- can do more complex things here
    }
};

C c1; // calls default constructor of C to create object c1
C c2 = C(); // calls default constructor explicitly
C c3(); // ERROR: this intuitive version is not possible due to "most vexing parse"
C c4{}; // but in C++11 {} CAN be used in a similar way

C c5[2]; // calls default constructor for both array elements
C* c6 = new C[2]; // calls default constructor for both array elements

Eine andere Möglichkeit, die Anforderung "keine Parameter" zu erfüllen, besteht darin, dass der Entwickler Standardwerte für alle Parameter bereitstellt:

class D{
    int i;
    int j;
public:
    // also a default constructor (can be called with no parameters)
    D( int i = 0, int j = 42 ) 
    : i(i), j(j){
    }
};


D d; // calls constructor of D with the provided default values for the parameters

Unter bestimmten Umständen (dh der Entwickler stellt keine Konstruktoren bereit und es gibt keine anderen disqualifizierenden Bedingungen), stellt der Compiler implizit einen leeren Standardkonstruktor bereit:

class C{
    std::string s; // note: members need to be default constructible themselves
};

C c1; // will succeed -- C has an implicitly defined default constructor

Einen anderen Konstruktor-Typ zu haben, ist eine der oben genannten Disqualifizierungsbedingungen:

class C{
    int i;
public:
    C( int i ) : i(i){}
};

C c1; // Compile ERROR: C has no (implicitly defined) default constructor
c ++ 11

Um die Erstellung eines impliziten Standardkonstruktors zu verhindern, ist es üblich, ihn als private zu deklarieren (ohne Definition). Die Absicht ist, einen Kompilierungsfehler zu verursachen, wenn jemand versucht, den Konstruktor zu verwenden (dies führt je nach Compiler entweder zu einem Zugriff auf private Fehler oder zu einem Linker-Fehler).

Um sicher zu sein, dass ein Standardkonstruktor (der dem impliziten funktionell ähnlich ist) definiert ist, könnte ein Entwickler explizit einen leeren Konstruktor schreiben.

c ++ 11

In C ++ 11 kann ein Entwickler auch das Schlüsselwort delete , um zu verhindern, dass der Compiler einen Standardkonstruktor bereitstellt.

class C{
    int i;
public:
    // default constructor is explicitly deleted
    C() = delete;
};

C c1; // Compile ERROR: C has its default constructor deleted

Darüber hinaus kann ein Entwickler auch explizit angeben, dass der Compiler einen Standardkonstruktor bereitstellen soll.

class C{
    int i;
public:
    // does have automatically generated default constructor (same as implicit one)
    C() = default;

    C( int i ) : i(i){}
};

C c1; // default constructed
C c2( 1 ); // constructed with the int taking constructor 
c ++ 14

Sie können mithilfe von std::is_default_constructible aus <type_traits> bestimmen, ob ein Typ über einen Standardkonstruktor verfügt (oder ein primitiver Typ ist):

class C1{ };
class C2{ public: C2(){} };
class C3{ public: C3(int){} };

using std::cout; using std::boolalpha; using std::endl;
using std::is_default_constructible;
cout << boolalpha << is_default_constructible<int>() << endl; // prints true
cout << boolalpha << is_default_constructible<C1>() << endl; // prints true
cout << boolalpha << is_default_constructible<C2>() << endl; // prints true
cout << boolalpha << is_default_constructible<C3>() << endl; // prints false
c ++ 11

In C ++ 11 ist es weiterhin möglich, die Nicht-Funktionsversion von std::is_default_constructible :

cout << boolalpha << is_default_constructible<C1>::value << endl; // prints true

Zerstörer

Ein Destruktor ist eine Funktion ohne Argumente, die aufgerufen wird, wenn ein benutzerdefiniertes Objekt zerstört werden soll. Es ist nach dem Typ benannt, den es mit einem ~ Präfix zerstört.

class C{
    int* is;
    string s;
public:
    C()
    : is( new int[10] ){
    }

    ~C(){  // destructor definition
        delete[] is;
    }
};

class C_child : public C{
    string s_ch;
public:
    C_child(){}
    ~C_child(){} // child destructor
};

void f(){
    C c1; // calls default constructor
    C c2[2]; // calls default constructor for both elements
    C* c3 = new C[2]; // calls default constructor for both array elements

    C_child c_ch;  // when destructed calls destructor of s_ch and of C base (and in turn s)

    delete[] c3; // calls destructors on c3[0] and c3[1]
} // automatic variables are destroyed here -- i.e. c1, c2 and c_ch

In den meisten Fällen (dh ein Benutzer stellt keinen Destruktor bereit, und es gibt keine anderen disqualifizierenden Bedingungen), stellt der Compiler implizit einen Standard-Destruktor bereit:

class C{
    int i;
    string s;
};

void f(){
    C* c1 = new C;
    delete c1; // C has a destructor
}

class C{
    int m;
private:
    ~C(){} // not public destructor!
};

class C_container{
    C c;
};

void f(){
    C_container* c_cont = new C_container;
    delete c_cont; // Compile ERROR: C has no accessible destructor
}
c ++ 11

In C ++ 11 kann ein Entwickler dieses Verhalten überschreiben, indem er verhindert, dass der Compiler einen Standard-Destruktor bereitstellt.

class C{
    int m;
public:
    ~C() = delete; // does NOT have implicit destructor
};

void f{
    C c1; 
} // Compile ERROR: C has no destructor

Darüber hinaus kann ein Entwickler auch explizit angeben, dass der Compiler einen Standard-Destruktor bereitstellen soll.

class C{
    int m;
public:
    ~C() = default; // saying explicitly it does have implicit/empty destructor
};

void f(){
    C c1;
} // C has a destructor -- c1 properly destroyed
c ++ 11

Sie können mit std::is_destructible aus <type_traits> feststellen, ob ein Typ einen Destruktor hat (oder ein primitiver Typ ist):

class C1{ };
class C2{ public: ~C2() = delete };
class C3 : public C2{ };

using std::cout; using std::boolalpha; using std::endl;
using std::is_destructible;
cout << boolalpha << is_destructible<int>() << endl; // prints true
cout << boolalpha << is_destructible<C1>() << endl; // prints true
cout << boolalpha << is_destructible<C2>() << endl; // prints false
cout << boolalpha << is_destructible<C3>() << endl; // prints false


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow