Zoeken…


Virtuele en beschermde destructors

Een klasse die is ontworpen om te worden geërfd, wordt een basisklasse genoemd. Wees voorzichtig met de speciale ledenfuncties van een dergelijke klasse.

Een klasse die is ontworpen om tijdens de uitvoering polymorf te worden gebruikt (via een wijzer naar de basisklasse) moet de destructor virtual verklaren. Hiermee kunnen de afgeleide delen van het object op de juiste manier worden vernietigd, zelfs wanneer het object via een aanwijzer naar de basisklasse wordt vernietigd.

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

Als de klasse niet polymorf hoeft te zijn, maar toch moet toestaan dat de interface wordt overgenomen, gebruik dan een niet-virtuele protected destructor.

class NonPolymorphicBase {
public:
    //    some methods

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

private:
    //    etc.
};

Zo'n klasse kan nooit door een wijzer worden vernietigd, waardoor stille lekken door snijden worden vermeden.

Deze techniek is met name van toepassing op klassen die zijn ontworpen als private basisklassen. Een dergelijke klasse kan worden gebruikt om enkele veelvoorkomende implementatiedetails in te kapselen, terwijl virtual methoden worden geboden als aanpassingspunten. Dit soort klasse mag nooit polymorf worden gebruikt, en een protected destructor helpt deze eis direct in de code te documenteren.

Ten slotte kunnen sommige klassen vereisen dat ze nooit als basisklasse worden gebruikt. In dit geval kan de klas als final worden gemarkeerd. Een normale niet-virtuele openbare destructor is in dit geval prima.

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

private:
    //    etc.
};

Impliciete verplaatsing en kopie

Houd er rekening mee dat het declareren van een destructor de compiler verhindert impliciete verplaatsingsconstructors en verplaatsingstoewijzers te genereren. Als u een destructor aangeeft, moet u ook de juiste definities toevoegen voor de verplaatsingshandelingen.

Bovendien zal het melden van verplaatsingshandelingen het genereren van kopieerbewerkingen onderdrukken, dus deze moeten ook worden toegevoegd (als de objecten van deze klasse een kopie-semantiek moeten hebben).

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

Kopiëren en ruilen

Als u een klasse schrijft die bronnen beheert, moet u alle speciale lidfuncties implementeren (zie Regel van Drie / Vijf / Nul ). De meest directe benadering voor het schrijven van de copyconstructor en de toewijzingsoperator is:

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

Maar deze aanpak heeft enkele problemen. Het ontbreekt de sterke uitzondering garantie - als new[] worpen, hebben we al het eigendom zijn van middelen vrijgemaakt this en kan niet herstellen. We dupliceren veel van de logica van het maken van kopieën bij het toewijzen van kopieën. En we moeten de zelftoewijzingscontrole onthouden, die meestal alleen maar overhead toevoegt aan de kopieerbewerking, maar nog steeds van cruciaal belang is.

Om aan de sterke uitzonderingsgarantie te voldoen en codeduplicatie te voorkomen (dubbel dus met de operator voor de volgende verplaatsingstoewijzing), kunnen we het idioom kopiëren en uitwisselen gebruiken:

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

Waarom werkt dit Bedenk wat er gebeurt als we dat hebben

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

Eerst kopiëren en construeren we rhs van p2 (die we hier niet hoefden te dupliceren). Als die bewerking gooit, doen we niets in operator= en blijft p1 onaangeroerd. Vervolgens wisselen we de leden tussen *this en rhs , en dan gaat rhs buiten bereik. Wanneer operator= , reinigt dit impliciet de oorspronkelijke bronnen this (via de destructor, die we niet hoefden te dupliceren). Zelftoewijzing werkt ook - het is minder efficiënt met copy-and-swap (vereist een extra toewijzing en deallocatie), maar als dat het onwaarschijnlijke scenario is, vertragen we de typische use case niet om het te verklaren.

C ++ 11

De bovenstaande formulering werkt zoals het al is voor toewijzing van verplaatsingen.

p1 = std::move(p2);

Hier, move-construct we rhs uit p2 , en al de rest is net zo geldig is. Als een klasse verplaatsbaar is maar niet kan worden gekopieerd, hoeft de kopie-toewijzing niet te worden verwijderd, omdat deze toewijzingsoperator eenvoudigweg slecht wordt gevormd vanwege de verwijderde kopieermaker.

Standaard Constructor

Een standaardconstructor is een type constructor die geen parameters vereist wanneer deze wordt aangeroepen. Het is vernoemd naar het type dat het construeert en is een lidfunctie ervan (zoals alle constructors zijn).

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

Een andere manier om aan de vereiste "geen parameters" te voldoen, is dat de ontwikkelaar standaardwaarden voor alle parameters levert:

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

Onder sommige omstandigheden (dat wil zeggen, de ontwikkelaar biedt geen constructors en er zijn geen andere diskwalificerende voorwaarden), biedt de compiler impliciet een lege standaardconstructor:

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

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

Het hebben van een ander type constructor is een van de eerder genoemde diskwalificerende voorwaarden:

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

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

Om impliciete standaardconstructorcreatie te voorkomen, is een veelgebruikte techniek om deze als private aan te duiden (zonder definitie). De bedoeling is om een compileerfout te veroorzaken wanneer iemand de constructor probeert te gebruiken (dit resulteert in een Access to private- fout of een linkerfout, afhankelijk van de compiler).

Om er zeker van te zijn dat een standaardconstructor (functioneel vergelijkbaar met de impliciete) is gedefinieerd, kan een ontwikkelaar een lege expliciet schrijven.

C ++ 11

In C ++ 11 kan een ontwikkelaar ook het sleutelwoord delete om te voorkomen dat de compiler een standaardconstructor levert.

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

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

Verder kan een ontwikkelaar ook expliciet zijn over het willen dat de compiler een standaardconstructor levert.

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

U kunt bepalen of een type een standaardconstructor heeft (of een primitief type is) met std::is_default_constructible uit <type_traits> :

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 is het nog steeds mogelijk om de niet-functor-versie van std::is_default_constructible :

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

destructor

Een destructor is een functie zonder argumenten die wordt opgeroepen wanneer een door de gebruiker gedefinieerd object wordt vernietigd. Het is vernoemd naar het type dat het vernietigt met een voorvoegsel ~ .

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

Onder de meeste omstandigheden (dwz een gebruiker biedt geen destructor en er zijn geen andere diskwalificerende voorwaarden), biedt de compiler impliciet een standaard destructor:

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 kan een ontwikkelaar dit gedrag negeren door te voorkomen dat de compiler een standaardvernietiger levert.

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

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

Verder kan een ontwikkelaar ook expliciet zijn over het willen dat de compiler een standaard destructor levert.

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

U kunt bepalen of een type een destructor heeft (of een primitief type is) met std::is_destructible van <type_traits> :

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow