C++
Funzioni dei membri virtuali
Ricerca…
Sintassi
virtual void f ();
virtual void g () = 0;
// C ++ 11 o successivo:
- virtual void h () override;
- void i () override;
- virtual void j () final;
- void k () final;
Osservazioni
- Solo le funzioni membro non statiche e non modello possono essere
virtual
. - Se si utilizza C ++ 11 o versioni successive, si consiglia di utilizzare l'
override
quando si esegue l'override di una funzione membro virtuale da una classe base. - Le classi di base polimorfiche hanno spesso distruttori virtuali per consentire l'eliminazione di un oggetto derivato tramite un puntatore alla classe base . Se il distruttore non era virtuale, tale operazione porta a un comportamento non definito [expr.delete] §5.3.5 / 3 .
Uso dell'override con virtuale in C ++ 11 e versioni successive
L' override
specificatore ha un significato speciale in C ++ 11 in poi, se aggiunto alla fine della firma della funzione. Questo significa che una funzione è
- Override della funzione presente nella classe base e
- La funzione di classe Base è
virtual
Non vi è alcun significato di run time
di questo identificatore poiché è inteso principalmente come indicazione per i compilatori
L'esempio seguente dimostrerà il cambiamento nel comportamento con il nostro senza utilizzare l'override.
Senza intervento 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"; }
};
Con 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"; }
};
Nota che l' override
non è una parola chiave, ma un identificatore speciale che può comparire solo nelle firme delle funzioni. In tutti gli altri contesti, l' override
può ancora essere utilizzato come identificatore:
void foo() {
int override = 1; // OK.
int virtual = 2; // Compilation error: keywords can't be used as identifiers.
}
Funzioni membro virtuale vs non virtuale
Con funzioni membro virtuali:
#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()"
}
Senza funzioni membro virtuali:
#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()"
}
Funzioni virtuali finali
C ++ 11 ha introdotto final
identificatore final
che vieta l'override del metodo se comparso nella firma del metodo:
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";
}
};
Lo specificatore final
può essere utilizzato solo con la funzione membro virtuale e non può essere applicato a funzioni membro non virtuali
Come final
, c'è anche un 'override' del chiamante che impedisce la sovrascrittura delle funzioni virtual
nella classe derivata.
Gli specifiers override
e final
possono essere combinati insieme per ottenere l'effetto desiderato:
class Derived1 : public Base {
public:
void foo() final override {
std::cout << "Derived1::Foo\n";
}
};
Comportamento delle funzioni virtuali in costruttori e distruttori
Il comportamento delle funzioni virtuali nei costruttori e nei distruttori spesso confonde quando viene rilevato per la prima volta.
#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;
}
Produzione:
Quando chiamato dal costruttore di base, viene chiamato base :: v ().
Quando chiamato dal costruttore derivato, viene chiamato derivato :: v ().
Quando viene chiamato dal derivato derivato, viene chiamato derivato :: v ().
Quando viene chiamato dal distruttore di base, viene chiamato base :: v ().
Il ragionamento dietro questo è che la classe derivata può definire membri aggiuntivi che non sono ancora inizializzati (nel caso del costruttore) o già distrutti (nel caso del distruttore) e chiamare le sue funzioni membro non sarebbe sicuro. Pertanto durante la costruzione e la distruzione di oggetti C ++, il tipo dinamico di *this
è considerato come la classe del costruttore o del distruttore e non una classe più derivata.
Esempio:
#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 funzioni virtuali
Possiamo anche specificare che una funzione virtual
è pura virtuale (astratta), aggiungendo = 0
alla dichiarazione. Le classi con una o più funzioni virtuali pure sono considerate astratte e non possono essere istanziate; solo le classi derivate che definiscono o ereditano le definizioni per tutte le funzioni virtuali pure possono essere istanziate.
struct Abstract {
virtual void f() = 0;
};
struct Concrete {
void f() override {}
};
Abstract a; // Error.
Concrete c; // Good.
Anche se una funzione è specificata come pura virtualità, può essere fornita un'implementazione predefinita. Nonostante ciò, la funzione sarà ancora considerata astratta e le classi derivate dovranno definirla prima che possano essere istanziate. In questo caso, la versione della funzione derivata della classe è anche autorizzata a chiamare la versione della classe base.
struct DefaultAbstract {
virtual void f() = 0;
};
void DefaultAbstract::f() {}
struct WhyWouldWeDoThis : DefaultAbstract {
void f() override { DefaultAbstract::f(); }
};
Ci sono un paio di motivi per cui potremmo voler fare questo:
Se vogliamo creare una classe che non può essere istanziata da sola, ma non impedisce l'istanziazione delle sue classi derivate, possiamo dichiarare il distruttore come virtuale puro. Essendo il distruttore, deve essere definito comunque, se vogliamo essere in grado di deallocare l'istanza. E poiché il distruttore è probabilmente già virtuale per prevenire perdite di memoria durante l'uso polimorfico , non si verificherà un colpo di prestazioni non necessario dal dichiarare un'altra funzione
virtual
. Questo può essere utile quando si creano interfacce.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.
Se la maggior parte o tutte le implementazioni della pura funzione virtuale conterranno il codice duplicato, tale codice può invece essere spostato nella versione della classe base, rendendo più semplice il mantenimento del codice.
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.