From 10.4 Abstract Classes parag. 6 in the Standard :
\"Member functions can be called from a constructor (or destructor) of an abstract class; the effe
I think this code is an example of the undefined behaviour referenced by the standard. In particular, it is not easy for the compiler to notice that this is undefined.
(BTW, when I say 'compiler', I really mean 'compiler and linker'. Apologies for any confusion.)
struct Abstract {
virtual void pure() = 0;
virtual void foo() {
pure();
}
Abstract() {
foo();
}
~Abstract() {
foo();
}
};
struct X : public Abstract {
virtual void pure() { cout << " X :: pure() " << endl; }
virtual void impure() { cout << " X :: impure() " << endl; }
};
int main() {
X x;
}
If the constructor of Abstract
directly called pure()
, this would obviously be a problem and a compiler can easily see that there is no Abstract::pure()
to be called, and g++ gives a warning. But in this example, the constructor calls foo()
, and foo()
is a non-pure virtual function. Therefore, there is no straightforward basis for the compiler or linker to give a warning or error.
As onlookers, we can see that foo
is a problem if called from the constructor of Abstract. Abstract::foo()
itself is defined, but it tries to call Abstract::pure
and this doesn't exist.
At this stage, you might think that the compiler should issue a warning/error about foo
on the grounds that it calls a pure virtual function. But instead you should consider the derived non-abstract class where pure
has been given an implementation. If you call foo
on that class after construction (and assuming you haven't overriden foo
), then you will get well-defined behaviour. So again, there is no basis for a warning about foo. foo
is well-defined as long as it isn't called in the constructor of Abstract
.
Therefore, each method (the constructor and foo) are each relatively OK if you look on them on their own. The only reason we know there is a problem is because we can see the big picture. A very smart compiler would put each particular implementation/non-implementation into one of three categories:
foo
that has an implementation but which might backfire depending on the status of the methods it calls.It's a lot of work to expect a compiler and linker to track all this, and hence the standard allows compilers to compile it cleanly but give undefined behaviour.
(I haven't mentioned the fact that it is possible to give implementations to pure-virtual methods. This is new to me. Is it defined properly, or is it just a compiler-specific extension? void Abstract :: pure() { }
)
So, it's not merely undefined 'because the standard says so`. You have to ask yourself 'what behaviour would you define for the above code?'. The only sensible answer is either to leave it undefined or to mandate a run-time error. The compiler and linker won't find it easy to analyse all these dependencies.
And to make matters worse, consider pointers-to-member-functions! The compiler or linker can't really tell if the 'problematic' methods will ever be called - it might depend on a whole load of other things that happen at runtime. If the compiler sees (this->*mem_fun)()
in the constructor, it can't be expected to know how well-defined mem_fun
is.