C++
Funkcje wirtualnego członka
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
-
virtual
mogą być tylko niestatyczne, nie będące szablonami funkcjevirtual
. - Jeśli używasz C ++ 11 lub nowszego, zaleca się użycie
override
podczasoverride
wirtualnej funkcji składowej z klasy podstawowej. - Polimorficzne klasy podstawowe często mają wirtualne destruktory, aby umożliwić usunięcie obiektu pochodnego przez wskaźnik do klasy podstawowej . Jeśli destruktor nie był wirtualny, taka operacja prowadzi do nieokreślonego zachowania [np. Usuń] §5.3.5 / 3 .
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.