![]() | |
![]() ![]() |
![]() | Imperfect C++ Practical Solutions for Real-Life Programming By Matthew Wilson |
Table of Contents | |
Chapter 22. Bolt-ins |
22.3. Nonvirtual OverridingWhen writing bolt-ins (and veneers also, on occasion) that are aimed at a general model of primary parameterizing type, it can be the case that you may be hiding, rather than overriding, for some of the targeted types. Overriding nonvirtual functions is usually a much frowned-upon activity, and quite rightly, too. Doing so can cause the instance of a class to have different behavior depending on where it is used. Consider the tradingRegister class in Listing 22.1, which has not been designed to act as a base class because it's Add() method is nonvirtual. Listing 22.1.class TradingRegister { public: void Add(Trade const &trade); // Adds a trade to the trading reg }; void DoTrade(TradingRegister ®, Trade const &trade) { reg.Add(trade); } Subsequently, someone has need to override the functionality provided in the guise of the LoggingTradingRegister, shown in Listing 22.2. Unfortunately, passing the LoggingTradingRegister instance to DoTrade() causes an error, in that the Add() method from tradingRegister is the one that is called. Listing 22.2.
class LoggingTradingRegistr
: public TradingRegistr
{
public:
void Add(Trade const &trade); // Adds a trade and logs it
};
Trade trade;
LoggingTradingRegister ltr;
DoTrade(ltr, trade); // Nasty. Trade doesn't get logged
This is the result of C++'s type resolution and inheritance mechanisms working together to be able to interpret instances of a type as that type, or as any of its publicly derived parent types. Without these mechanisms, C++ would be a pretty useless language, but it does mean that we should avoid overriding nonvirtual mechanisms in general. However, where there is no chance of the type being used via its parent type, there is no problem with non-virtual overriding. This is the case when using templates. Consider if DoTrade()was expressed as a template:
template< typename T
, typename U
>
void DoTrade(T ®, U const &trade)
{
reg.Add(trade);
}
. . .
DoTrade(ltr, trade); // Fine. Trade is logged
This form of DoTrade() does not, and in fact cannot, know about the parent class(es) of T. Hence, the instantiation of DoTrade()knows only that ltr is of type LoggingTradingRegister, so it can only call LoggingTradingRegister::Add(). Although using them with templates is safe, types that employ nonvirtual overriding can also be used in other nontemplate contexts, in which case they still represent a danger. The problem with any technique that employs template derivation—whether that be veneers or bolt-ins or any similar mechanisms—is that experience shows that it can be very easy to break the rule against hiding when you're designing templates with a large potential set of parameterizing types. This is a significant flaw in these techniques, and one must be mindful of it when using them. In practice, users must treat such components as a white box just as they must when using libraries based on run time polymorphism.[2]
![]() |
![]() | |
![]() ![]() |