C++
Virtuele ledenfuncties
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
- Alleen niet-statische, niet-sjabloonlidfuncties kunnen
virtual
. - Als u C ++ 11 of hoger gebruikt, wordt het aanbevolen om te
override
wanneer u een functie voor een virtueel lidoverride
vanuit een basisklasse. - Polymorfe basisklassen hebben vaak virtuele destructors waarmee een afgeleid object kan worden verwijderd via een aanwijzer naar de basisklasse . Als de destructor niet virtueel was, leidt een dergelijke operatie tot ongedefinieerd gedrag [expr.delete] §5.3.5 / 3 .
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.