Bill T
Posts: 3
Nickname: billt
Registered: Jun, 2007
|
|
Re: Pure Virtual Function Called: An Explanation
|
Posted: Jun 8, 2007 6:37 AM
|
|
There's another scenario where this can happen that is common in multi-threaded, event-driven code:
Assume an abstract class that defines an interface (call it A), a base class (call it B) that derives from A, and that can be derived from, and a derived class (call it D) that represents what a user would write, and which derives from B.
Assume that B does not define the pure virtual method declared in A -- for instance, to force the derived class to implement it.
The pure virtual method is a "callback" method -- i.e., it is invoked in response to external events, and on a different thread.
If another thread invokes D's dtor while a callback is in progress, some "funny" things can happen:
* D's dtor executes -- the object is still a "D" at this point.
* At the end of D's dtor (but before ~D returns) B's dtor executes, since it is the next-most derived class. At this point the compiler replaces the vtable ptr in the object with B's vtable ptr, so the object is now a "B", not a "D" anymore. Remember that B chose not to define the pure virtual method, in order to force it to be defined in D.
* Another thread invokes the callback function on the object in response to an external event. At this point you get a "pure virtual error", because B's vtable doesn't have an entry for the callback method -- B's dtor effectively changed the object from a "D" to a "B".
One possible solution to this problem is to use a lock to control access to the dtor and callback function. This doesn't work, however, unless the lock is acquired in the most-derived class ("D") -- by the time the object morphs into a "B" (at entry to B's dtor) it is already too late.
If the code in question is a library (our case), it may not be reasonable to expect users of the convenience class to have to remember to lock the object in their dtor.
For this reason, it seemed to us that the only reasonable solution was to not define the callback method as pure virtual, but as a "plain old" virtual method, with a default (no-op) implementation. When the callback function is invoked in response to the external event, at least there is a vtable entry for the compiler to call. This assumes, of course, that the callback method can be skipped -- not an unreasonable assumption for an object that is halfway deleted already anyway.
|
|