Sök…


Syntax

  • virtuellt tomrum f ();

  • virtuellt tomrum g () = 0;

  • // C ++ 11 eller senare:

    • virtual void h () åsidosättande;
    • void i () åsidosättande;
    • virtual void j () final;
    • void k () final;

Anmärkningar

Använda åsidosättande med virtuella i C ++ 11 och senare

Specifikationen override har en speciell betydelse i C ++ 11 och framåt, om den bifogas i slutet av funktionssignaturen. Detta betyder att en funktion är

  • Överstyr funktionen som finns i basklassen &
  • Basklassfunktionen är virtual

Det finns ingen betydelse av run time för denna specifikator, eftersom den huvudsakligen är tänkt som en indikation för kompilatorer

Exemplet nedan visar förändringsuppförandet med vårt utan att använda åsidosättande.

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

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

Observera att override inte är ett nyckelord utan en speciell identifierare som bara kan visas i funktionssignaturer. I alla andra sammanhang kan override fortfarande användas som identifierare:

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

Virtuella och icke-virtuella medlemsfunktioner

Med virtuella medlemsfunktioner:

#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()"
}

Utan virtuella medlemsfunktioner:

#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()"
}

Slutliga virtuella funktioner

C ++ 11 introducerade den final specifikatorn som förbjuder åsidosättande av metod om den visas i metodsignatur:

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

Den specifice final kan bara användas med `virtuell medlemsfunktion och kan inte tillämpas på icke-virtuella medlemsfunktioner

Precis som final , finns det också en specifik anropare som "åsidosätter" som förhindrar åsidosättande av virtual funktioner i den härledda klassen.

Specifikatorerna override och final kan kombineras tillsammans för att ha önskad effekt:

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

Beteende med virtuella funktioner hos konstruktörer och destruktorer

Uppträdandet av virtuella funktioner hos konstruktörer och destruktorer är ofta förvirrande när man först möter det.

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

Produktion:

När den kallas från baskonstruktören kommer bas :: v () att ringas.
När den heter från den härledda konstruktören, kommer den :: v () att ringas.
När den kallas från härledd destruktor, härleds :: v ().
När den kallas från basdestruktorn, kommer bas :: v () att ringas.

Resonemanget bakom detta är att den härledda klassen kan definiera ytterligare medlemmar som ännu inte är initierade (i konstruktionsfallet) eller redan förstörda (i destruktorns fall) och att det skulle vara osäkert att ringa dess medlemsfunktioner. Därför anses den dynamiska typen av *this under konstruktion och förstörelse av C ++ -objekt vara konstruktörens eller förstörarklass och inte en mer härledd klass.

Exempel:

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

Rena virtuella funktioner

Vi kan också specificera att en virtual funktion är ren virtuell (abstrakt) genom att lägga till = 0 till deklarationen. Klasser med en eller flera rena virtuella funktioner anses vara abstrakta och kan inte instanseras; endast härledda klasser som definierar eller ärver definitioner för alla rena virtuella funktioner kan inställas.

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

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

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

Även om en funktion anges som ren virtuell, kan den ges en standardimplementering. Trots detta kommer funktionen fortfarande att betraktas som abstrakt, och härledda klasser måste definiera den innan de kan instanseras. I det här fallet tillåts den härledda klassens "version av funktionen till och med att kalla basklassen" -versionen.

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

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

Det finns några orsaker till att vi kanske vill göra detta:

  • Om vi vill skapa en klass som inte själva kan instanseras, men inte hindrar dess härledda klasser från att instanseras, kan vi förklara förstöraren som ren virtuell. Som förstörare måste det definieras hur som helst om vi vill kunna omlokalisera instansen. Och eftersom förstöraren troligen redan är virtuell för att förhindra minnesläckage under polymorf användning , kommer vi inte att drabbas av en onödig prestanda från att förklara en annan funktion som virtual . Detta kan vara användbart när du skapar gränssnitt.

      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.
    
  • Om de flesta eller alla implementeringar av den rena virtuella funktionen kommer att innehålla duplikatkod, kan den koden istället flyttas till basklassversionen, vilket gör koden lättare att underhålla.

      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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow