Sök…


Virtuella och skyddade destruktorer

En klass designad för att ärva-från kallas en basklass. Man bör vara försiktig med de speciella medlemsfunktionerna i en sådan klass.

En klass utformad för att användas polymorfiskt vid körning (genom en pekare till basklassen) bör förklara förstöraren som virtual . Detta tillåter att de härledda delarna av objektet förstörs ordentligt, även när objektet förstörs genom en pekare till basklassen.

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

Om klassen inte behöver vara polymorf, men fortfarande behöver tillåta att gränssnittet ärvs, använd en icke-virtuell protected destruktor.

class NonPolymorphicBase {
public:
    //    some methods

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

private:
    //    etc.
};

En sådan klass kan aldrig förstöras genom en pekare och undvika tyst läckage på grund av skivning.

Denna teknik gäller särskilt klasser designade för att vara private basklasser. En sådan klass kan användas för att kapsla in vissa vanliga implementeringsdetaljer, samtidigt som de tillhandahåller virtual metoder som anpassningspunkter. Denna typ av klass bör aldrig användas polymorfiskt, och en protected förstörare hjälper till att dokumentera detta krav direkt i koden.

Slutligen kan vissa klasser kräva att de aldrig används som basklass. I detta fall kan klassen markeras som final . En vanlig icke-virtuell offentlig förstörare är bra i detta fall.

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

private:
    //    etc.
};

Implicit Flytta och kopiera

Kom ihåg att förklara en förstörare hindrar kompilatorn från att generera implicita flyttkonstruktörer och flytta uppdragsoperatörer. Om du förklarar en förstörare, kom ihåg att också lägga till lämpliga definitioner för flyttoperationerna.

Dessutom kommer att förklara flyttningsoperationer att undertrycka genereringen av kopieringsoperationer, så dessa bör också läggas till (om objektet i denna klass måste ha kopisemantik).

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

Kopiera och byt

Om du skriver en klass som hanterar resurser måste du implementera alla specialmedlemfunktioner (se regel om tre / fem / noll ). Det mest direkta sättet att skriva kopieringskonstruktören och uppdragsoperatören skulle vara:

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

Men detta tillvägagångssätt har vissa problem. Det misslyckas med den starka undantagsgarantin - om new[] kastar, har vi redan rensat resurserna som ägs av this och kan inte återhämta sig. Vi duplicerar mycket av logiken för kopieringskonstruktion i kopieringsuppdrag. Och vi måste komma ihåg självtilldelningskontrollen, som vanligtvis bara lägger omkostnader för kopieringen, men som fortfarande är kritisk.

För att tillfredsställa den starka undantagsgarantin och undvika kodduplicering (dubbelt så med den efterföljande operatören för flyttilldelning) kan vi använda kopierings- och swap-formen:

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

Varför fungerar det här? Tänk på vad som händer när vi har det

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

Först kopierar vi rhs från p2 (som vi inte behövde kopiera här). Om den operationen kastar, gör vi ingenting i operator= och p1 förblir orört. Därefter byter vi medlemmarna mellan *this och rhs , och sedan går rhs utanför räckvidden. När operator= rensar det implicit de ursprungliga resurserna för this (via destruktorn, som vi inte behövde kopiera). Självtilldelning fungerar också - det är mindre effektivt med kopiering och byte (innebär en extra allokering och omlokalisering), men om det är det osannolika scenariot drar vi inte ner det typiska fallet för att redogöra för det.

C ++ 11

Ovanstående formulering fungerar som den redan är för flytttilldelning.

p1 = std::move(p2);

Här flyttar vi-Construct rhs från p2 , och resten är lika giltiga. Om en klass är rörlig men inte kopierbar, finns det inget behov av att ta bort kopieringsuppdraget, eftersom denna tilldelningsoperatör helt enkelt blir felformad på grund av den borttagna kopikonstruktören.

Standardkonstruktör

En standardkonstruktör är en typ av konstruktör som inte kräver några parametrar när den anropas. Den är uppkallad efter den typ den konstruerar och är en medlemsfunktion för den (som alla konstruktörer är).

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

Ett annat sätt att uppfylla kravet på "inga parametrar" är att utvecklaren tillhandahåller standardvärden för alla parametrar:

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

Under vissa omständigheter (dvs. utvecklaren tillhandahåller inga konstruktörer och det finns inga andra diskvalificerande villkor) ger kompilatorn implicit en tom standardkonstruktör:

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

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

Att ha någon annan typ av konstruktör är ett av de diskvalificerande villkoren som nämnts tidigare:

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

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

För att förhindra implicit standardkonstruktörskapning är en vanlig teknik att förklara den som private (utan definition). Avsikten är att orsaka ett kompileringsfel när någon försöker använda konstruktören (detta leder antingen till ett Access to private error eller ett linker-fel, beroende på kompilatorn).

För att vara säker på att en standardkonstruktör (funktionellt liknar den implicita) är definierad, kan en utvecklare skriva ett tomt uttryckligt.

c ++ 11

I C ++ 11 kan en utvecklare också använda sökordet delete att förhindra kompilatorn från att tillhandahålla en standardkonstruktör.

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

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

Dessutom kan en utvecklare också vara uttrycklig för att vilja att kompilatorn ska tillhandahålla en standardkonstruktör.

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

Du kan bestämma om en typ har en standardkonstruktör (eller är en primitiv typ) med std::is_default_constructible från <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

I C ++ 11 är det fortfarande möjligt att använda den icke-funktorversionen av std::is_default_constructible :

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

destructor

En destruktor är en funktion utan argument som kallas när ett användardefinierat objekt är på väg att förstöras. Den är uppkallad efter den typ som den förstör med ett ~ prefix.

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

Under de flesta omständigheter (dvs. en användare tillhandahåller ingen förstörare och det finns inga andra diskvalificerande villkor) ger kompilatorn en standarddestruktor implicit:

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

I C ++ 11 kan en utvecklare åsidosätta detta beteende genom att förhindra kompilatorn från att tillhandahålla en standardförstörare.

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

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

Dessutom kan en utvecklare också vara uttrycklig för att vilja att kompilatorn ska tillhandahålla en standardförstörare.

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

Du kan bestämma om en typ har en förstörare (eller är en primitiv typ) med std::is_destructible från <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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow