When’s the last time you saw code like this?
Did you:
Or.
It’s probably pretty obvious from today’s title that my answer would be "B".
The abstract reason for this is that Foo::DoSomething() violates (the spirit of) the Single Responsibility Principle.
This can be seen by thinking about who the customers (I say "customer" because "client" is a loaded term) are of public methods, and who are the customers of virtual methods.
The answer for public methods is easy: if you think of the class as providing a set of services, it’s the clients of the services, i.e. the users of the class. The answer for virtual methods isn’t much harder: it’s the implementers of derived classes.
In other words, public method declarations are an interface mechanism for informing clients about the services of the class; virtual method declarations are an interface mechanism for telling implementers about the behaviors of the class which can be specialized.
A public virtual method is trying to serve two masters who might have conflicting requirements – this is usually a bad idea, as can be seen by the predicament that the poor gentlemen below has found himself in.
The practical reason for answer "B" is a flow control issue: execution flow never passes through the base class when a virtual method is called. This means that the base class has absolutely no ability to enforce common behaviors among the derived classes.
This in turn often leads to a set of rules that must be remembered in order to correctly override the base class methods. For example, when’s the last time you saw this save idiom:
Because Foo1::Save is virtual, the application requires the implementer of Boo1 to learn and remember to save the base class data.
This is a burden on the poor implementer (who often has to remember a different set of rules for each class) and a recipe for bugs when someone forgets something.
The way to solve this problem is to split the responsibilities between two methods: A public method that clients call and a virtual method that implementers override.
In contrast to the Foo1 example, there is all sorts of good stuff going on in Foo2:
Two subtleties:
1) You might have noticed that I declared Foo2::SaveSubClassData as private.
This is legal C++, and it’s preferred over declaring SaveSubClassData as protected.
The reason is that protected methods have a similar responsibility to public methods: they provide a set of services which derived classes can make use of. In other words, even derived classes should only call virtual methods through non-virtual base-class wrappers.
2) There is one exception to the above discussion.
The case where Foo is a pure interface class (analogous to a C# interface). By this I mean Foo has no data members, only pure virtual methods, and is used in a multiple-inheritance “mix-in” context to emulate the “implements” mechanism of e.g. C#.
The point of an interface class is that it’s a mechanism for client code to specify the services it requires; this is subtly different from the responsibility of public methods specifying the services the class supplies and corresponds to a single responsibility.
In particular, there’s no implementation code associated with an interface – control jumps straight to the implementer, which is exactly what happens with pure virtual methods.
These Stories on 3D Software Development Kits
ACIS, 3DScript and SAT are registered trademarks of Spatial Corp.
No Comments Yet
Let us know what you think