Zoeken…


Syntaxis

  • virtuele leegte f ();

  • virtuele leegte g () = 0;

  • // C ++ 11 of nieuwer:

    • virtuele leegte h () opheffen;
    • void i () opheffen;
    • virtuele leegte j () finale;
    • void k () finaal;

Opmerkingen

Override gebruiken met virtual in C ++ 11 en hoger

De override heeft een speciale betekenis in C ++ 11 en hoger, indien toegevoegd aan het einde van de functiehandtekening. Dit betekent dat een functie is

  • De aanwezige functie in de basisklasse overschrijven &
  • De functie Base class is virtual

Er is geen run time significantie van deze specificatie, zoals hoofdzakelijk bedoeld als een indicatie voor compilers

In het onderstaande voorbeeld wordt de gedragsverandering bij ons getoond zonder override te gebruiken.

Zonder override :

#include <iostream>

struct X {
    virtual void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
    // Y::f() will not override X::f() because it has a different signature,
    // but the compiler will accept the code (and silently ignore Y::f()).
    virtual void f(int a) { std::cout << a << "\n"; }
};

Met override :

#include <iostream>

struct X {
    virtual void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
    // The compiler will alert you to the fact that Y::f() does not
    // actually override anything.
    virtual void f(int a) override { std::cout << a << "\n"; }
};

Houd er rekening mee dat override geen trefwoord is, maar een speciale identificatie die alleen in functietekeningen kan voorkomen. In alle andere contexten kan override nog steeds als identificatie worden gebruikt:

void foo() {
    int override = 1; // OK.
    int virtual = 2;  // Compilation error: keywords can't be used as identifiers.
}

Virtuele versus niet-virtuele lidfuncties

Met virtuele ledenfuncties:

#include <iostream>

struct X {
    virtual void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
    // Specifying virtual again here is optional
    // because it can be inferred from X::f().
    virtual void f() { std::cout << "Y::f()\n"; } 
};

void call(X& a) {
    a.f();
}

int main() {
    X x;
    Y y;
    call(x); // outputs "X::f()"
    call(y); // outputs "Y::f()"
}

Zonder virtuele ledenfuncties:

#include <iostream>

struct X {
   void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
   void f() { std::cout << "Y::f()\n"; }
};

void call(X& a) {
    a.f();
}

int main() {
    X x;
    Y y;
    call(x); // outputs "X::f()"
    call(y); // outputs "X::f()"
}

Laatste virtuele functies

C ++ 11 introduceerde final specificatie die het overschrijven van de methode verbiedt als deze in de methode-handtekening verscheen:

class Base {
public:
    virtual void foo() {
        std::cout << "Base::Foo\n";
    }
};

class Derived1 : public Base {
public:
    // Overriding Base::foo
    void foo() final {
        std::cout << "Derived1::Foo\n";
    }
};

class Derived2 : public Derived1 {
public:
    // Compilation error: cannot override final method
    virtual void foo() {
        std::cout << "Derived2::Foo\n";
    }
};

De specificatie- final kan alleen worden gebruikt met de functie `virtuele 'leden en kan niet worden toegepast op niet-virtuele ledenfuncties

Net als final is er ook een specificator-beller 'override' die het negeren van virtual functies in de afgeleide klasse voorkomt.

De override en final specificatie kunnen worden gecombineerd om het gewenste effect te hebben:

class Derived1 : public Base {
public:
    void foo() final override {
        std::cout << "Derived1::Foo\n";
    }
};

Gedrag van virtuele functies in constructors en destructors

Het gedrag van virtuele functies in constructors en destructors is vaak verwarrend wanneer het voor het eerst wordt aangetroffen.

#include <iostream>
using namespace std;

class base { 
public:
    base() { f("base constructor"); }
    ~base() { f("base destructor"); }

    virtual const char* v() { return "base::v()"; }

    void f(const char* caller) { 
        cout << "When called from " << caller << ", "  << v() << " gets called.\n"; 
    }        
};

class derived : public base {
public:
    derived() { f("derived constructor"); }
    ~derived() { f("derived destructor"); }

    const char* v() override { return "derived::v()"; }

};

int main() {
     derived d;
}

Output:

Wanneer het wordt aangeroepen vanuit base constructor, wordt base :: v () aangeroepen.
Wanneer dit wordt afgeleid van afgeleide constructor, wordt afgeleid :: v () aangeroepen.
Wanneer afgeleid van afgeleid destructor, wordt afgeleid :: v () aangeroepen.
Wanneer het wordt aangeroepen vanuit base destructor, wordt base :: v () aangeroepen.

De redenering hierachter is dat de afgeleide klasse extra leden kan definiëren die nog niet zijn geïnitialiseerd (in het geval van de constructor) of al zijn vernietigd (in het geval van de destructor), en het oproepen van de lidfuncties zou onveilig zijn. Daarom wordt het dynamische type *this tijdens constructie en vernietiging van C ++ -objecten beschouwd als de klasse van de constructor of de destructor en niet als een meer afgeleide klasse.

Voorbeeld:

#include <iostream>
#include <memory>

using namespace std;
class base {
public:
    base()
    {
        std::cout << "foo is " << foo() << std::endl;
    }
    virtual int foo() { return 42; }
};

class derived : public base {
    unique_ptr<int> ptr_;
public:
    derived(int i) : ptr_(new int(i*i)) { }
    // The following cannot be called before derived::derived due to how C++ behaves, 
    // if it was possible... Kaboom!
    int foo() override   { return *ptr_; } 
};

int main() {
    derived d(4);
}

Pure virtuele functies

We kunnen ook opgeven dat een virtual functie puur virtueel (abstract) is, door = 0 te voegen aan de aangifte. Klassen met een of meer pure virtuele functies worden als abstract beschouwd en kunnen niet worden geïnstantieerd; alleen afgeleide klassen die definities definiëren of overnemen voor alle zuivere virtuele functies kunnen worden geïnstantieerd.

struct Abstract {
    virtual void f() = 0;
};

struct Concrete {
    void f() override {}
};

Abstract a; // Error.
Concrete c; // Good.

Zelfs als een functie is opgegeven als pure virtueel, kan deze een standaardimplementatie krijgen. Desondanks wordt de functie nog steeds als abstract beschouwd en moeten afgeleide klassen deze eerst definiëren voordat ze kunnen worden geïnstantieerd. In dit geval mag de afgeleide klasse 'versie van de functie zelfs de versie van de basisklasse aanroepen.

struct DefaultAbstract {
    virtual void f() = 0;
};
void DefaultAbstract::f() {}

struct WhyWouldWeDoThis : DefaultAbstract {
    void f() override { DefaultAbstract::f(); }
};

Er zijn een aantal redenen waarom we dit misschien willen doen:

  • Als we een klasse willen maken die zelf niet kan worden geïnstantieerd, maar niet voorkomt dat de afgeleide klassen worden geïnstantieerd, kunnen we de destructor verklaren als puur virtueel. Als destructor moet het hoe dan ook worden gedefinieerd als we de instantie willen kunnen dealloceren. En aangezien de destructor waarschijnlijk al virtueel is om geheugenlekken tijdens polymorf gebruik te voorkomen , zullen we geen onnodige prestatiehit oplopen door een andere functie virtual verklaren. Dit kan handig zijn bij het maken van interfaces.

      struct Interface {
          virtual ~Interface() = 0;
      };
      Interface::~Interface() = default;
    
      struct Implementation : Interface {};
      // ~Implementation() is automatically defined by the compiler if not explicitly
      //  specified, meeting the "must be defined before instantiation" requirement.
    
  • Als de meeste of alle implementaties van de pure virtuele functie dubbele code bevatten, kan die code in plaats daarvan worden verplaatst naar de basisklasse-versie, waardoor de code gemakkelijker te onderhouden is.

      class SharedBase {
          State my_state;
          std::unique_ptr<Helper> my_helper;
          // ...
    
        public:
          virtual void config(const Context& cont) = 0;
          // ...
      };
      /* virtual */ void SharedBase::config(const Context& cont) {
          my_helper = new Helper(my_state, cont.relevant_field);
          do_this();
          and_that();
      }
    
      class OneImplementation : public SharedBase {
          int i;
          // ...
    
        public:
          void config(const Context& cont) override;
          // ...
      };
      void OneImplementation::config(const Context& cont) /* override */ {
          my_state = { cont.some_field, cont.another_field, i };
          SharedBase::config(cont);
          my_unique_setup();
      };
    
      // And so on, for other classes derived from SharedBase.
    


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow