Calling a function using the B::foo
syntax is perfectly legal in general.
The exception detailed in the C++ standard is that when you have a pure virtual function that you do not call via B::foo
, you do not have to define it. 10.4/2: (via @jrok)
[...] A pure virtual function need be defined only if called with, or as with (12.4), the qualified-id syntax (5.1). [...]
The B::foo()
syntax is an example of calling foo
with the qualified-id syntax. Note that this is distinct from the (this->*&B::foo)()
syntax, which does a virtual table lookup.
A pure virtual function is not "setting the function to a null pointer" despite the =0
syntax. Rather, it is marking it as "in order to instantiate this class, I require that a descendent override this method". As it happens, compilers tend to set the virtual function table of a pure virtual function to point to an error handler that sets up an error (at least in debugging) that you made a pure virtual function call, because that is pretty cheap to set up, and it diagnoses calls to said method during construction/destruction before or after the descendent class instance lifetimes.
You can mark a method as pure virtual even if your parent defined it. This just indicates that your child implementations have to override it again. You can mark a method as pure virtual even if you define it. Again, this just tells your child implementations that they have to override it.
As a bonus answer, why would we do this? Sometimes you want to implement a partial implementation in a base class, to be invoked before or after the child implementation. Other times you want a default implementation. In each case, you want some code to be coupled to this method, and you want to force children to override it.
You could implement the before/after/default in a distinct non-virtual
method called before_foo
or default_foo
, or you could just do this.
At other times, in a deep hierarchy the implementation of some method inherited from above may no longer be valid. So you'll =0
it to indicate that children have to override it again.
Both cases are extreme corner cases.
The final spot I've used this technique is when I wanted to do away with the (bar->*&Bar::foo)()
annoyance. I override Bar::foo
to do (this->*&Bar::foo)()
, allowing bar->Bar::foo()
to do the right thing "magically".