Sök…


Definiera polymorfa klasser

Det typiska exemplet är en abstrakt formklass som sedan kan härledas till rutor, cirklar och andra konkreta former.

Förälderklassen:

Låt oss börja med den polymorfa klassen:

class Shape {
public:
    virtual ~Shape() = default;
    virtual double get_surface() const = 0;
    virtual void describe_object() const { std::cout << "this is a shape" << std::endl; }  

    double get_doubled_surface() const { return 2 * get_surface(); } 
};

Hur läser jag denna definition?

  • Du kan definiera polymorfiskt beteende genom introducerade medlemsfunktioner med nyckelordet virtual . Här get_surface() och describe_object() kommer givetvis att genomföras på olika sätt för en kvadrat än en cirkel. När funktionen åberopas på ett objekt kommer funktion som motsvarar objektets verkliga klass att bestämmas vid körning.

  • Det är meningslöst att definiera get_surface() för en abstrakt form. Därför följs funktionen av = 0 . Detta betyder att funktionen är ren virtuell funktion .

  • En polymorf klass bör alltid definiera en virtuell förstörare.

  • Du kan definiera icke-virtuella medlemsfunktioner. När dessa funktioner kommer att anropas för ett objekt, kommer funktionen att väljas beroende på klassen som används vid sammanställningstiden. Här get_double_surface() på detta sätt.

  • En klass som innehåller minst en ren virtuell funktion är en abstrakt klass. Abstrakta klasser kan inte instanseras. Du kanske bara har pekare eller referenser av en abstrakt klasstyp.

Deriverade klasser

När en polymorf basklass har definierats kan du härleda den. Till exempel:

class Square : public Shape {
    Point top_left;
    double side_length;
public: 
    Square (const Point& top_left, double side)
       : top_left(top_left), side_length(side_length) {}

    double get_surface() override { return side_length * side_length; }   
    void describe_object() override { 
        std::cout << "this is a square starting at " << top_left.x << ", " << top_left.y
                  << " with a length of " << side_length << std::endl; 
    }  
};

Några förklaringar:

  • Du kan definiera eller åsidosätta någon av de virtuella funktionerna i föräldraklassen. Det faktum att en funktion var virtuell i förälderklassen gör den virtuell i den härledda klassen. Du behöver inte säga kompilatorn nyckelordet virtual igen. Men det rekommenderas att lägga till nyckelordet override i slutet av funktionsdeklarationen för att förhindra subtila buggar orsakade av obemärkta variationer i funktionssignaturen.
  • Om alla de rena virtuella funktionerna i förälderklassen definieras kan du instansera objekt för den här klassen, annars blir det också en abstrakt klass.
  • Du är inte skyldig att åsidosätta alla virtuella funktioner. Du kan behålla versionen av föräldern om den passar ditt behov.

Exempel på instansering

int main() {

    Square square(Point(10.0, 0.0), 6); // we know it's a square, the compiler also
    square.describe_object(); 
    std::cout << "Surface: " << square.get_surface() << std::endl; 

    Circle circle(Point(0.0, 0.0), 5);

    Shape *ps = nullptr;  // we don't know yet the real type of the object
    ps = &circle;         // it's a circle, but it could as well be a square
    ps->describe_object(); 
    std::cout << "Surface: " << ps->get_surface() << std::endl;
}

Säker nedkastning

Anta att du har en pekare till ett objekt i en polymorf klass:

Shape *ps;                       // see example on defining a polymorphic class
ps =  get_a_new_random_shape();  // if you don't have such a function yet, you 
                                 // could just write ps = new Square(0.0,0.0, 5);

en nedkastning skulle vara att kasta från en allmän polymorf Shape till en av dess härledda och mer specifika form som Square eller Circle .

Varför nedställa?

För det mesta skulle du inte behöva veta vilken är den verkliga typen av objekt, eftersom de virtuella funktionerna låter dig manipulera ditt objekt oberoende av dess typ:

std::cout << "Surface: " << ps->get_surface() << std::endl; 

Om du inte behöver någon nedskärning skulle din design vara perfekt.

Det kan dock hända att du ibland behöver nedställa. Ett typiskt exempel är när du vill åberopa en icke virtuell funktion som endast finns för barnklassen.

Tänk till exempel cirklar. Endast cirklar har en diameter. Så klassen skulle definieras som:

class Circle: public Shape { // for Shape, see example on defining a polymorphic class
    Point center;
    double radius;
public: 
    Circle (const Point& center, double radius)
       : center(center), radius(radius) {}

    double get_surface() const override { return r * r * M_PI; }   

    // this is only for circles. Makes no sense for other shapes 
    double get_diameter() const { return 2 * r; }
};

get_diameter() finns bara för cirklar. Det definierades inte för ett Shape objekt:

Shape* ps = get_any_shape();
ps->get_diameter(); // OUCH !!! Compilation error 

Hur nedställer jag?

Om du med säkerhet vet att ps pekar på en cirkel kan du välja en static_cast :

std::cout << "Diameter: " << static_cast<Circle*>(ps)->get_diameter() << std::endl;

Detta kommer att göra susen. Men det är väldigt riskabelt: om ps verkar av något annat än en Circle din kods beteende att vara definierat.

Så snarare än att spela rysk roulette, bör du säkert använda en dynamic_cast . Detta är specifikt för polymorfa klasser:

int main() {
    Circle circle(Point(0.0, 0.0), 10);
    Shape &shape = circle;

    std::cout << "The shape has a surface of " << shape.get_surface() << std::endl;

    //shape.get_diameter();   // OUCH !!! Compilation error 

    Circle *pc = dynamic_cast<Circle*>(&shape); // will be nullptr if ps wasn't a circle 
    if (pc) 
        std::cout << "The shape is a circle of diameter " << pc->get_diameter() << std::endl;
    else
        std::cout << "The shape isn't a circle !" << std::endl; 
}        

Observera att dynamic_cast inte är möjligt i en klass som inte är polymorf. Du behöver minst en virtuell funktion i klassen eller dess föräldrar för att kunna använda den.

Polymorphism & Destructors

Om en klass är avsedd att användas polymorf, med härledda instanser som lagras som baspekare / referenser, bör dess basklassens förstörare vara antingen virtual eller protected . I det förra fallet kommer detta att förorsaka objektförstörelse för att kontrollera vtable , automatiskt ringa rätt destruktor baserat på den dynamiska typen. I det senare fallet är förstörelse av objektet genom en basklasspekare / referens inaktiverat, och objektet kan endast raderas när det uttryckligen behandlas som dess faktiska typ.

struct VirtualDestructor {
    virtual ~VirtualDestructor() = default;
};

struct VirtualDerived : VirtualDestructor {};

struct ProtectedDestructor {
  protected:
    ~ProtectedDestructor() = default;
};

struct ProtectedDerived : ProtectedDestructor {
    ~ProtectedDerived() = default;
};

// ...

VirtualDestructor* vd = new VirtualDerived;
delete vd; // Looks up VirtualDestructor::~VirtualDestructor() in vtable, sees it's
           // VirtualDerived::~VirtualDerived(), calls that.

ProtectedDestructor* pd = new ProtectedDerived;
delete pd; // Error: ProtectedDestructor::~ProtectedDestructor() is protected.
delete static_cast<ProtectedDerived*>(pd); // Good.

Båda dessa metoder garanterar att den härledda klassens förstörare alltid kommer att kallas i härledda klassinstanser, vilket förhindrar minnesläckage.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow