I recently came across code of the following kind:
struct A {
virtual void foo() = 0;
};
struct B : public A {
virtual void foo() = 0;
};
struct C : public B {
virtual void foo() override {
}
};
Is there any purpose in redeclaring a pure virtual method?
Even more importantly: Does this change semantics? I.e is B::foo shadowing A::foo and introducing a new vftable?
Whats happens in the above example if A::foo is actually not pure virtual but provides an implementation (still compiles for me)?
Look at the example from cppreference:
struct Abstract { virtual void f() = 0; // pure virtual }; // "Abstract" is abstract struct Concrete : Abstract { void f() override {} // non-pure virtual virtual void g(); // non-pure virtual }; // "Concrete" is non-abstract struct Abstract2 : Concrete { void g() override = 0; // pure virtual overrider }; // "Abstract2" is abstract int main() { // Abstract a; // Error: abstract class Concrete b; // OK Abstract& a = b; // OK to reference abstract base a.f(); // virtual dispatch to Concrete::f() // Abstract2 a2; // Error: abstract class (final overrider of g() is pure) }
Is there any purpose in redeclaring a pure virtual method?
There is a purpose in declaring a virtual method as pure in a derived class: Abstract2 can decalre g to be pure virtual even if it is not pure in Concrete.
Does this change semantics? I.e is B::foo shadowing A::foo and introducing a new vftable?
No.
Whats happens in the above example if A::foo is actually not pure virtual but provides an implementation (still compiles for me)?
See the example.
You do not have to delcare a method as virtual in a derived class. When it is declared virtual in the base it is also virtual in derived classes. In a nutshell, the =0 just tells the compiler that there is not necessarily a definition and that the class is abstract. Your example is the same as:
struct AA { // abstract
virtual void foo() = 0;
};
struct BB : public AA { }; // abstract
struct CC : public BB { // concrete
void foo() override {}
};
The only difference I am aware of is the type of BB::foo:
// above
std::cout << std::is_same< decltype(&BB::foo), void (AA::*)()>::value;
std::cout << std::is_same< decltype(&BB::foo), void (BB::*)()>::value;
std::cout << std::is_same< decltype(&BB::foo), void (CC::*)()>::value << "\n";
// your example
std::cout << std::is_same< decltype(&B::foo), void (A::*)()>::value;
std::cout << std::is_same< decltype(&B::foo), void (B::*)()>::value;
std::cout << std::is_same< decltype(&B::foo), void (C::*)()>::value << "\n";
Output is:
100
010
That is, redeclaring foo in B introduces the name foo directly in B. B::foo is a member function of B. On the other hand in my example BB::foo is the method from AA. However, this doesn't affect C::foo.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With