c++ überschreiben - Warum brauchen wir virtuelle Funktionen in C ++?





python virtueller (18)


Wenn Sie eine Funktion in der Basisklasse haben, können Sie sie in der abgeleiteten Klasse neu Redefine oder Override .

Methode neu definieren : In der abgeleiteten Klasse wird eine neue Implementierung für die Methode der Basisklasse angegeben. Erleichtert nicht die Dynamic binding .

Überschreiben einer Methode : Redefining einer virtual method der Basisklasse in der abgeleiteten Klasse. Die virtuelle Methode erleichtert die dynamische Bindung .

Also als du gesagt hast:

Aber früher im Buch, als ich etwas über grundlegende Vererbung lernte, war ich in der Lage, Basismethoden in abgeleiteten Klassen zu überschreiben, ohne "virtuell" zu verwenden.

Sie haben es nicht überschrieben, da die Methode in der Basisklasse nicht virtuell war, sondern Sie sie neu definiert haben

Ich lerne C ++ und komme gerade in virtuelle Funktionen.

Was ich (im Buch und online) gelesen habe, sind virtuelle Funktionen Funktionen in der Basisklasse, die Sie in abgeleiteten Klassen überschreiben können.

Aber früher im Buch, als ich etwas über die grundlegende Vererbung lernte, war ich in der Lage, Basisfunktionen in abgeleiteten Klassen ohne Verwendung von virtual zu überschreiben.

Also was fehlt mir hier? Ich weiß, dass es mehr virtuelle Funktionen gibt, und es scheint wichtig zu sein, also möchte ich klarstellen, was genau das ist. Ich kann einfach keine direkte Antwort online finden.




Wenn die Basisklasse Base ist und eine abgeleitete Klasse Der , können Sie einen Base *p Zeiger haben, der tatsächlich auf eine Instanz von Der . Wenn Sie p->foo(); aufrufen p->foo(); Wenn foo nicht virtuell ist, dann wird die Version von Base ausgeführt, wobei ignoriert wird, dass p tatsächlich auf einen Der . Wenn foo virtuell ist, führt p->foo() das "leafmost" -Überschreiben von foo , wobei die tatsächliche Klasse des pointierten Objekts vollständig berücksichtigt wird. Daher ist der Unterschied zwischen virtuell und nicht-virtuell ziemlich entscheidend: erstere erlauben den Laufzeit- polymorphism , das Kernkonzept der OO-Programmierung, während die anderen dies nicht tun.




Über die Effizienz sind die virtuellen Funktionen etwas weniger effizient als die frühen Bindungsfunktionen.

Dieser virtuelle Aufrufmechanismus kann fast so effizient wie der Mechanismus des "normalen Funktionsaufrufs" gemacht werden (innerhalb von 25%). Sein Platzbedarf ist ein Zeiger in jedem Objekt einer Klasse mit virtuellen Funktionen plus ein Vtbl für jede derartige Klasse Tour von C ++ durch Bjarne Stroustrup




Sie benötigen mindestens 1 Vererbung und einen Downcast, um dies zu demonstrieren. Hier ist ein sehr einfaches Beispiel:

class Animal
{        
    public: 
      // turn the following virtual modifier on/off to see what happens
      //virtual   
      std::string Says() { return "?"; }  
};

class Dog: public Animal
{
    public: std::string Says() { return "Woof"; }
};

void test()
{
    Dog* d = new Dog();
    Animal* a = d;       // refer to Dog instance with Animal pointer

    cout << d->Says();   // always Woof
    cout << a->Says();   // Woof or ?, depends on virtual
}



Sie benötigen virtuelle Methoden für sicheres Downcasting , Einfachheit und Prägnanz .

Das ist es, was virtuelle Methoden tun: Sie reduzieren sicher, mit scheinbar einfachem und präzisem Code, und vermeiden die unsicheren manuellen Umwandlungen in dem komplexeren und ausführlicheren Code, den Sie sonst hätten.

Nicht-virtuelle Methode ⇒ statische Bindung

Der folgende Code ist absichtlich "falsch". Es deklariert die value Methode nicht als virtual und erzeugt daher ein unbeabsichtigtes "falsches" Ergebnis, nämlich 0:

#include <iostream>
using namespace std;

class Expression
{
public:
    auto value() const
        -> double
    { return 0.0; }         // This should never be invoked, really.
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const
        -> double
    { return number_; }     // This is OK.

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const
        -> double
    { return a_->value() + b_->value(); }       // Uhm, bad! Very bad!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

In der als "schlecht" kommentierten Zeile wird die Expression::value Methode aufgerufen, weil der statisch bekannte Typ (der zur Kompilierungszeit bekannte Typ ) Expression ist und die value nicht virtuell ist.

Virtuelle Methode ⇒ dynamische Bindung.

Deklarieren von value als virtual in dem statisch bekannten Typ Expression stellt sicher, dass jeder Aufruf den tatsächlichen Objekttyp überprüft, und ruft die relevante Implementierung des value für diesen dynamischen Typ auf :

#include <iostream>
using namespace std;

class Expression
{
public:
    virtual
    auto value() const -> double
        = 0;
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const -> double
        override
    { return number_; }

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const -> double
        override
    { return a_->value() + b_->value(); }    // Dynamic binding, OK!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

Hier ist die Ausgabe 6.86 wie es sein sollte, da die virtuelle Methode virtuell aufgerufen wird . Dies wird auch dynamisches Binden der Aufrufe genannt. Eine kleine Überprüfung wird durchgeführt, wobei der tatsächliche dynamische Objekttyp und die relevante Methodenimplementierung für diesen dynamischen Typ ermittelt werden.

Die relevante Implementierung ist diejenige in der spezifischsten (am weitesten abgeleiteten) Klasse.

Beachten Sie, dass Methodenimplementierungen in abgeleiteten Klassen hier nicht als virtual markiert sind, sondern stattdessen override . Sie könnten als virtual markiert werden, aber sie sind automatisch virtuell. Das Schlüsselwort override stellt sicher, dass, wenn es in einer Basisklasse keine solche virtuelle Methode gibt, ein Fehler auftritt (was wünschenswert ist).

Die Hässlichkeit, dies ohne virtuelle Methoden zu tun

Ohne virtual müsste man eine Do It Yourself- Version der dynamischen Bindung implementieren. Dies beinhaltet in der Regel unsicheres manuelles Downcasting, Komplexität und Ausführlichkeit.

Für den Fall einer einzelnen Funktion, wie hier, ist es ausreichend, einen Funktionszeiger im Objekt zu speichern und über diesen Funktionszeiger aufzurufen, aber trotzdem beinhaltet es einige unsichere Downcasts, Komplexität und Ausführlichkeit, nämlich:

#include <iostream>
using namespace std;

class Expression
{
protected:
    typedef auto Value_func( Expression const* ) -> double;

    Value_func* value_func_;

public:
    auto value() const
        -> double
    { return value_func_( this ); }

    Expression(): value_func_( nullptr ) {}     // Like a pure virtual.
};

class Number
    : public Expression
{
private:
    double  number_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    { return static_cast<Number const*>( expr )->number_; }

public:
    Number( double const number )
        : Expression()
        , number_( number )
    { value_func_ = &Number::specific_value_func; }
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    {
        auto const p_self  = static_cast<Sum const*>( expr );
        return p_self->a_->value() + p_self->b_->value();
    }

public:
    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    { value_func_ = &Sum::specific_value_func; }
};


auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

Eine positive Möglichkeit, dies zu betrachten, ist, dass, wenn Sie unsicheres Downcasting, Komplexität und Ausführlichkeit wie oben erfahren, dann oft eine virtuelle Methode oder Methoden wirklich helfen können.




Ohne "virtuell" erhält man "früh verbindlich". Welche Implementierung der Methode verwendet wird, wird zur Kompilierzeit basierend auf dem Typ des Zeigers entschieden, den Sie aufrufen.

Mit "virtuell" erhalten Sie "späte Bindung". Welche Implementierung der Methode verwendet wird, wird zur Laufzeit basierend auf dem Typ des zu zeigenden Objekts entschieden - woraus es ursprünglich konstruiert wurde. Dies ist nicht notwendigerweise der, den Sie aufgrund des Typs des Zeigers meinen, der auf dieses Objekt zeigt.

class Base
{
  public:
            void Method1 ()  {  std::cout << "Base::Method1" << std::endl;  }
    virtual void Method2 ()  {  std::cout << "Base::Method2" << std::endl;  }
};

class Derived : public Base
{
  public:
    void Method1 ()  {  std::cout << "Derived::Method1" << std::endl;  }
    void Method2 ()  {  std::cout << "Derived::Method2" << std::endl;  }
};

Base* obj = new Derived ();
  //  Note - constructed as Derived, but pointer stored as Base*

obj->Method1 ();  //  Prints "Base::Method1"
obj->Method2 ();  //  Prints "Derived::Method2"

EDIT - siehe diese Frage .

Außerdem - dieses Tutorial behandelt die frühe und späte Bindung in C ++.




Warum brauchen wir virtuelle Methoden in C ++?

Schnelle Antwort:

  1. Es liefert uns eine der benötigten "Zutaten" 1 für die objektorientierte Programmierung .

In Bjarne Stroustrup C ++ Programmierung: Prinzipien und Praxis, (14.3):

Die virtuelle Funktion bietet die Möglichkeit, eine Funktion in einer Basisklasse zu definieren und eine Funktion mit demselben Namen und Typ in einer abgeleiteten Klasse zu verwenden, die aufgerufen wird, wenn ein Benutzer die Basisklassenfunktion aufruft. Dies wird oft als Laufzeitpolymorphismus , dynamischer Dispatch oder Laufzeitversand bezeichnet, da die aufgerufene Funktion zur Laufzeit basierend auf dem Typ des verwendeten Objekts ermittelt wird.

  1. Es ist die schnellste und effizienteste Implementierung, wenn Sie einen virtuellen Funktionsaufruf benötigen.

Um einen virtuellen Anruf zu behandeln, benötigt man ein oder mehrere Datenstücke, die sich auf das abgeleitete Objekt 3 beziehen. Die übliche Vorgehensweise besteht darin, die Adresse der Funktionstabelle hinzuzufügen. Diese Tabelle wird normalerweise als virtuelle Tabelle oder virtuelle Funktionstabelle bezeichnet und ihre Adresse wird oft als virtueller Zeiger bezeichnet . Jede virtuelle Funktion erhält einen Slot in der virtuellen Tabelle. Abhängig vom Objekttyp (abgeleitet) des Aufrufers ruft die virtuelle Funktion ihrerseits die entsprechende Überschreibung auf.

1. Die Verwendung von Vererbung, Laufzeitpolymorphismus und Kapselung ist die gebräuchlichste Definition von objektorientierter Programmierung .

2. Sie können die Funktionalität nicht so programmieren, dass sie schneller ist oder weniger Speicher belegt, wenn Sie andere Sprachfunktionen verwenden, um zur Laufzeit zwischen den Alternativen zu wählen. Bjarne Stroustrup C ++ Programmierung: Prinzipien und Praxis. (14.3.1) .

3. Etwas zu sagen, welche Funktion tatsächlich aufgerufen wird, wenn wir die Basisklasse aufrufen, die die virtuelle Funktion enthält.




Hier habe ich nicht nur verstanden, was virtual Funktionen sind, sondern auch warum sie benötigt werden:

Nehmen wir an, Sie haben diese zwei Klassen:

class Animal
{
    public:
        void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

In Ihrer Hauptfunktion:

Animal *animal = new Animal;
Cat *cat = new Cat;

animal->eat(); // Outputs: "I'm eating generic food."
cat->eat();    // Outputs: "I'm eating a rat."

So weit, so gut, oder? Tiere essen generisches Essen, Katzen essen Ratten, alle ohne virtual .

Lassen Sie uns jetzt ein wenig ändern, so dass eat() über eine Zwischenfunktion aufgerufen wird (eine triviale Funktion nur für dieses Beispiel):

// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }

Jetzt ist unsere Hauptfunktion:

Animal *animal = new Animal;
Cat *cat = new Cat;

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating generic food."

Oh oh ... wir haben eine Katze in func() , aber es wird keine Ratten essen. Solltest du func() überladen, so braucht es eine Cat* ? Wenn Sie mehr Tiere von Animal herleiten müssen, würden sie alle ihre eigenen Funktionen benötigen func() .

Die Lösung besteht darin, eat() aus der Animal Klasse eine virtuelle Funktion zu machen:

class Animal
{
    public:
        virtual void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

Main:

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating a rat."

Erledigt.




Warum brauchen wir virtuelle Funktionen?

Virtuelle Funktionen vermeiden unnötige Typumwandlungsprobleme, und einige von uns können darüber debattieren, warum wir virtuelle Funktionen benötigen, wenn wir den abgeleiteten Klassenzeiger verwenden können, um die Funktion in abgeleiteten Klassen aufzurufen! Die Antwort ist - sie hebt die Idee der Vererbung im großen System auf Entwicklung, wo es sehr wünschenswert ist, ein einzelnes Pointer-Basisklassenobjekt zu haben.

Lassen Sie uns im Folgenden zwei einfache Programme vergleichen, um die Bedeutung von virtuellen Funktionen zu verstehen:

Programm ohne virtuelle Funktionen:

#include <iostream>
using namespace std;

class father
{
    public: void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

AUSGABE:

Fathers age is 50 years
Fathers age is 50 years
son`s age is 26 years

Programm mit virtueller Funktion:

#include <iostream>
using namespace std;

class father
{
    public:
        virtual void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

AUSGABE:

Fathers age is 50 years
son`s age is 26 years
son`s age is 26 years

Durch die enge Analyse der beiden Ausgaben kann man die Bedeutung von virtuellen Funktionen verstehen.




Virtuelle Methoden werden im Interface Design verwendet. Zum Beispiel in Windows gibt es eine Schnittstelle namens IUnknown wie unten:

interface IUnknown {
  virtual HRESULT QueryInterface (REFIID riid, void **ppvObject) = 0;
  virtual ULONG   AddRef () = 0;
  virtual ULONG   Release () = 0;
};

Diese Methoden müssen vom Benutzer der Schnittstelle implementiert werden. Sie sind wesentlich für die Erstellung und Zerstörung bestimmter Objekte, die IUnknown erben müssen. In diesem Fall kennt die Laufzeit die drei Methoden und erwartet, dass sie beim Aufruf implementiert werden. In gewisser Weise wirken sie als ein Vertrag zwischen dem Objekt selbst und dem, was dieses Objekt benutzt.




Here is complete example that illustrates why virtual method is used.

#include <iostream>

using namespace std;

class Basic
{
    public:
    virtual void Test1()
    {
        cout << "Test1 from Basic." << endl;
    }
    virtual ~Basic(){};
};
class VariantA : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantA." << endl;
    }
};
class VariantB : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantB." << endl;
    }
};

int main()
{
    Basic *object;
    VariantA *vobjectA = new VariantA();
    VariantB *vobjectB = new VariantB();

    object=(Basic *) vobjectA;
    object->Test1();

    object=(Basic *) vobjectB;
    object->Test1();

    delete vobjectA;
    delete vobjectB;
    return 0;
}



Es hilft, wenn Sie die zugrunde liegenden Mechanismen kennen. C ++ formalisiert einige Codierungstechniken, die von C-Programmierern verwendet werden, "Klassen", die unter Verwendung von "Überlagerungen" ersetzt wurden - Strukturen mit gemeinsamen Kopfabschnitten würden verwendet werden, um Objekte verschiedener Typen, aber mit einigen gemeinsamen Daten oder Operationen zu behandeln. Normalerweise hat die Basisstruktur der Überlagerung (der gemeinsame Teil) einen Zeiger auf eine Funktionstabelle, die auf einen anderen Satz von Routinen für jeden Objekttyp zeigt. C ++ macht dasselbe, verbirgt aber die Mechanismen, dh C ++ ptr->func(...) wo func virtuell ist wie C (*ptr->func_table[func_num])(ptr,...) , wo sich was ändert zwischen abgeleiteten Klassen ist der Inhalt von func_table. [Eine nicht virtuelle Methode ptr-> func () wird nur in mangled_func (ptr, ..) übersetzt.]

Das Ergebnis davon ist, dass Sie nur die Basisklasse verstehen müssen, um die Methoden einer abgeleiteten Klasse aufzurufen, dh wenn eine Routine Klasse A versteht, können Sie ihr einen abgeleiteten Klassen-B-Zeiger übergeben, dann werden die aufgerufenen virtuellen Methoden diese sein von B eher als von A, da du durch die Funktionstabelle B hindurch gehst.




Wir brauchen virtuelle Methoden zur Unterstützung von "Laufzeitpolymorphismus". Wenn Sie mit einem Zeiger oder einem Verweis auf die Basisklasse auf ein abgeleitetes Klassenobjekt verweisen, können Sie eine virtuelle Funktion für dieses Objekt aufrufen und die abgeleitete Klassenversion der Funktion ausführen.




Das virtuelle Schlüsselwort erzwingt, dass der Compiler die Methodenimplementierung auswählt, die in der Klasse des Objekts und nicht in der Klasse des Zeigers definiert ist .

Shape *shape = new Triangle(); 
cout << shape->getName();

Im obigen Beispiel wird Shape :: getName standardmäßig aufgerufen, es sei denn, getName () ist in der Basisklasse Shape als virtuell definiert. Dies zwingt den Compiler, in der Triangle-Klasse statt in der Shape-Klasse nach der getName () - Implementierung zu suchen.

Die virtuelle Tabelle ist der Mechanismus, in dem der Compiler die verschiedenen Implementierungen der virtuellen Klassen der Unterklassen verfolgt. Dies wird auch dynamischer Versand genannt, und es ist ein gewisser Mehraufwand damit verbunden.

Schließlich, warum wird virtual sogar in C ++ benötigt, warum nicht das Standardverhalten wie in Java?

  1. C ++ basiert auf den Prinzipien "Zero Overhead" und "Pay for which you use". Es wird also nicht versucht, dynamischen Versand für Sie durchzuführen, es sei denn, Sie benötigen es.
  2. Um der Schnittstelle mehr Kontrolle zu geben. Wenn eine Funktion nicht virtuell ist, kann die Schnittstelle / abstrakte Klasse das Verhalten in allen Implementierungen steuern.



Ich habe meine Antwort in Form einer Konversation, um besser gelesen zu werden:

Warum brauchen wir virtuelle Funktionen?

Wegen Polymorphismus.

Was ist Polymorphismus?

Die Tatsache, dass ein Basiszeiger auch auf Objekte des abgeleiteten Typs zeigen kann.

Wie führt diese Definition von Polymorphismus dazu, dass virtuelle Funktionen benötigt werden?

Nun, durch frühe Bindung .

Was ist früh verbindlich?

Frühe Bindung (Compile-Time-Bindung) in C ++ bedeutet, dass ein Funktionsaufruf vor der Ausführung des Programms behoben wird.

Damit...?

Wenn Sie also einen Basistyp als Parameter einer Funktion verwenden, erkennt der Compiler nur die Basisschnittstelle, und wenn Sie diese Funktion mit beliebigen Argumenten aus abgeleiteten Klassen aufrufen, wird sie abgeschnitten, was nicht der Fall ist.

Wenn es nicht das ist, was wir wollen, warum ist das erlaubt?

Weil wir Polymorphismus brauchen!

Was ist der Vorteil von Polymorphismus dann?

Sie können einen Basistyp-Zeiger als Parameter einer einzelnen Funktion verwenden, und dann können Sie in der Laufzeit Ihres Programms auf alle Schnittstellen des abgeleiteten Typs (z. B. ihre Member-Funktionen) ohne Probleme zugreifen, indem Sie die Dereferenzierung dieser Single verwenden Basiszeiger.

Ich weiß immer noch nicht, wofür virtuelle Funktionen gut sind ...! Und das war meine erste Frage!

Nun, das ist, weil Sie Ihre Frage zu früh gestellt haben!

Warum brauchen wir virtuelle Funktionen?

Angenommen, Sie haben eine Funktion mit einem Basiszeiger aufgerufen, der die Adresse eines Objekts aus einer seiner abgeleiteten Klassen hatte. Wie oben erwähnt, wird dieser Zeiger in der Laufzeit dereferenziert, so weit, so gut, aber wir erwarten, dass eine Methode (== eine Memberfunktion) "von unserer abgeleiteten Klasse" ausgeführt wird! Allerdings ist dieselbe Methode (eine, die denselben Header hat) bereits in der Basisklasse definiert. Warum sollte Ihr Programm also die andere Methode wählen? Mit anderen Worten, ich meine, wie können Sie dieses Szenario von dem unterscheiden, was wir früher normalerweise gesehen haben?

Die kurze Antwort ist "eine virtuelle Mitgliedsfunktion in der Basis", und eine etwas längere Antwort lautet: "Wenn das Programm eine virtuelle Funktion in der Basisklasse sieht, weiß es (realisiert), dass Sie es verwenden wollen Polymorphismus "und so geht es zu abgeleiteten Klassen (mit v-table , eine Form der späten Bindung), um diese andere Methode mit dem gleichen Header, aber mit - erwarteter - anderer Implementierung zu finden.

Warum eine andere Implementierung?

Du Knöchelkopf! Geh und lies ein gutes Buch !

OK, warte, warte, warum sollte man sich bemühen, Basiszeiger zu verwenden, wenn er / sie einfach abgeleitete Typzeiger verwenden könnte? Du bist der Richter, sind all diese Kopfschmerzen es wert? Schau dir diese zwei Schnipsel an:

// 1:

Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();

// 2:

Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();

OK, obwohl ich denke, dass 1 immer noch besser als 2 ist , könntest du 1 auch so schreiben:

// 1:

Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();

und außerdem solltest du dir bewusst sein, dass dies nur eine erfundene Anwendung all der Dinge ist, die ich dir bisher erklärt habe. Nehmen Sie stattdessen zum Beispiel eine Situation an, in der Sie in Ihrem Programm eine Funktion hatten, die die Methoden jeder abgeleiteten Klasse verwendet (getMonthBenefit ()):

double totalMonthBenefit = 0;    
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
     totalMonthBenefit += x -> getMonthBenefit();
}

Versuchen Sie jetzt, dies neu zu schreiben, ohne irgendwelche Kopfschmerzen!

double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();

Und tatsächlich könnte dies auch ein künstliches Beispiel sein!




Notwendigkeit für virtuelle Funktion erklärt [einfach zu verstehen]

#include<iostream>

using namespace std;

class A{
public: 
        void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
     void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B; // Create a base class pointer and assign address of derived object.
    a1->show();

}

Ausgabe wird sein:

Hello from Class A.

Aber mit virtueller Funktion:

#include<iostream>

using namespace std;

class A{
public:
    virtual void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
    virtual void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B;
    a1->show();

}

Ausgabe wird sein:

Hello from Class B.

Daher können Sie mit der virtuellen Funktion Laufzeitpolymorphie erreichen.




Sie müssen zwischen Übersteuern und Überladen unterscheiden. Ohne das virtual Schlüsselwort überlasten Sie nur eine Methode einer Basisklasse. Das bedeutet nichts anderes als sich zu verstecken. Nehmen wir an, Sie haben eine Basisklasse Base und eine abgeleitete Klasse Specialized die beide void foo() implementieren. Jetzt haben Sie einen Zeiger auf Base , der auf eine Instanz von Specialized . Wenn Sie foo() aufrufen, können Sie den Unterschied beobachten, den virtual macht: Wenn die Methode virtuell ist, wird die Implementierung von Specialized verwendet, wenn sie fehlt, wird die Version von Base ausgewählt. Es empfiehlt sich, Methoden niemals von einer Basisklasse zu überladen. Eine Methode, die nicht virtuell ist, ist die Methode ihres Autors, um zu sagen, dass ihre Erweiterung in Unterklassen nicht beabsichtigt ist.




Das ist sicher!...

Durch die Verzweigungsvorhersage wird die Logik langsamer ausgeführt, da in Ihrem Code eine Umschaltung erfolgt! Es ist, als würden Sie eine gerade Straße oder eine Straße mit vielen Abbiegungen gehen. Die gerade Straße wird sicher schneller gemacht! ...

Wenn das Array sortiert ist, ist Ihre Bedingung im ersten Schritt falsch: data[c] >= 128und wird dann für den gesamten Weg bis zum Ende der Straße ein wahrer Wert. So kommen Sie schneller ans Ende der Logik. Auf der anderen Seite, wenn Sie ein unsortiertes Array verwenden, müssen Sie viel drehen und bearbeiten, wodurch Ihr Code sicher langsamer läuft ...

Schauen Sie sich das Bild an, das ich für Sie erstellt habe. Welche Straße wird schneller fertig sein?

So programmgesteuert bewirkt die Verzweigungsvorhersage, dass der Prozess langsamer wird ...

Am Ende ist es auch gut zu wissen, dass wir zwei Arten von Verzweigungsvorhersagen haben, die sich jeweils unterschiedlich auf Ihren Code auswirken:

1. Statisch

2. Dynamisch

Die statische Verzweigungsvorhersage wird vom Mikroprozessor verwendet, wenn zum ersten Mal eine bedingte Verzweigung auftritt, und die dynamische Verzweigungsvorhersage wird für nachfolgende Ausführungen des bedingten Verzweigungscodes verwendet.

Um Ihren Code effektiv zu schreiben, um diese Regeln zu nutzen, sollten Sie beim Schreiben von if-else- oder switch- Anweisungen zuerst die häufigsten Fälle prüfen und schrittweise auf die wenigsten Fälle zurückgreifen. Schleifen erfordern nicht unbedingt eine spezielle Code-Reihenfolge für die statische Verzweigungsvorhersage, da normalerweise nur der Zustand des Schleifeniterators verwendet wird.





c++ virtual-functions