Szukaj…


Składnia

  • virtual void f ();

  • wirtualna pustka g () = 0;

  • // C ++ 11 lub nowszy:

    • wirtualna zmiana void h ();
    • void i () override;
    • virtual void j () final;
    • void k () final;

Uwagi

Używanie przesłonięcia z wirtualnym w C ++ 11 i nowszych

override specyfikatora ma specjalne znaczenie w C ++ 11 i późniejszych, jeśli jest dołączane na końcu podpisu funkcji. Oznacza to, że funkcja jest

  • Przesłanianie funkcji obecnej w klasie bazowej i
  • Funkcja klasy podstawowej jest virtual

Ten specyfikator nie ma znaczenia w run time wykonywania, ponieważ ma on głównie służyć jako wskazówka dla kompilatorów

Poniższy przykład zademonstruje zmianę w naszym zachowaniu bez stosowania zastępowania.

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

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

Zauważ, że override nie jest słowem kluczowym, ale specjalnym identyfikatorem, który może występować tylko w podpisach funkcji. We wszystkich innych kontekstach override nadal może być użyte jako identyfikator:

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

Wirtualne a nie-wirtualne funkcje składowe

Dzięki wirtualnym funkcjom członkowskim:

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

Bez wirtualnych funkcji członka:

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

Ostateczne funkcje wirtualne

C ++ 11 wprowadził final specyfikator, który zabrania zastępowania metody, jeśli pojawił się w sygnaturze metody:

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

Specyfikator final może być stosowane tylko w `wirtualnej” funkcji składowej i nie mogą być stosowane do funkcji składowych bez wirtualnego

Podobnie jak final , istnieje również „override” wywołującego specyfikator, który zapobiega przesłonięciu funkcji virtual w klasie pochodnej.

override specyfikatorów i final można łączyć ze sobą, aby uzyskać pożądany efekt:

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

Zachowanie funkcji wirtualnych w konstruktorach i destruktorach

Zachowanie funkcji wirtualnych w konstruktorach i destruktorach jest często mylące przy pierwszym spotkaniu.

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

Wynik:

Po wywołaniu z konstruktora podstawowego wywoływana jest funkcja base :: v ().
Wywoływany z konstruktora pochodnego wywoływany jest pochodna :: v ().
Po wywołaniu z pochodnego destruktora wywoływana jest pochodna :: v ().
Po wywołaniu z podstawowego destruktora wywoływana jest funkcja base :: v ().

Powodem tego jest to, że klasa pochodna może zdefiniować dodatkowe elementy, które nie zostały jeszcze zainicjowane (w przypadku konstruktora) lub już zniszczone (w przypadku destruktora), a wywoływanie funkcji składowych byłoby niebezpieczne. Dlatego podczas budowy i niszczenia obiektów C ++ dynamiczny typ *this jest uważany za klasę konstruktora lub destruktora, a nie klasę bardziej pochodną.

Przykład:

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

Czyste funkcje wirtualne

Możemy również określić, że funkcja virtual jest czysto wirtualna (abstrakcyjna), dodając = 0 do deklaracji. Klasy z jedną lub kilkoma czystymi funkcjami wirtualnymi są uważane za abstrakcyjne i nie mogą być tworzone; tworzone są tylko klasy pochodne, które definiują lub dziedziczą definicje wszystkich czystych funkcji wirtualnych.

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

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

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

Nawet jeśli funkcja jest określona jako czysta wirtualna, może ona otrzymać domyślną implementację. Mimo to funkcja będzie nadal uważana za abstrakcyjną, a klasy pochodne będą musiały ją zdefiniować, zanim będą mogły zostać utworzone. W tym przypadku wersja funkcji pochodnej klasy może nawet wywoływać wersję klasy podstawowej.

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

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

Istnieje kilka powodów, dla których możemy chcieć to zrobić:

  • Jeśli chcemy stworzyć klasę, której nie można utworzyć instancji, ale nie uniemożliwia tworzenia instancji klas pochodnych, możemy zadeklarować destruktor jako czystą wirtualną. Będąc niszczycielem, i tak trzeba go zdefiniować, jeśli chcemy móc cofnąć przydział instancji. A ponieważ destruktor najprawdopodobniej jest już wirtualny, aby zapobiec wyciekom pamięci podczas użytkowania polimorficznego , nie ponosimy niepotrzebnego spadku wydajności spowodowanego ogłoszeniem innej funkcji virtual . Może to być przydatne podczas tworzenia interfejsów.

      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.
    
  • Jeśli większość lub wszystkie implementacje funkcji czysto wirtualnej będą zawierały zduplikowany kod, kod ten można zamiast tego przenieść do wersji klasy podstawowej, co ułatwia utrzymanie kodu.

      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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow