|
-
June 30th, 2005, 02:00 AM
#36
Re: extending an STL container class Question
 Originally Posted by wien
But to make a class meant to be used as a base class, without any virtual functions is somewhat of a design-flaw in my opinion. What would then be the point in deriving from it? Code reuse? In that case I refer to the quotes Graham posted.
But which functions must be virtual?
Consider the class complex which represents a complex number.
Which functions should be virtual?
To allow the best flexibility the solution is to define an abstract base class (not doing so don't allow to change the numbers representation) containing these virtual functions:
real, imag, conj, norm, arg, pow, operator+(), operator-(). abs, cos, sin, exp, log, log10, tan, atan, tanh, sinh, cosh, acos, asin.
The binary operators should also be defined.
Many of these functions can have a default implementation that use one of the base accessors : real, imag, arg, norm and conj.
For binary operators, that is more complex, because even if we let them pure in the base class, in the derived classes we must choose to return a representation depending on the first parameter of the operator (that is the this pointer), but not depending on the second parameter.
Now, we want to implement 4 derived classes.
ScalarFloatComplex (with a representation containing a float real part, and a float imaginary part)
PolarFloatComplex (containing floats for argument and magnitude).
ScalarDoubleComplex
PolarDoubleComplex
Note that the accessors of the base class, are all returning double.
Now the real problem comes in the implementation of binary operators.
We want that a ScalarFloatComplex+a ScalarDoubleComplex returns a ScalarDoubleComplex and not a ScalarFloatComplex.
But in the ScalarFloatComplex: perator*(complex &x) we cannot know if x uses floats or doubles without doing RTTI (but doing RTTI breaks the genericity), so we choose to return a ScalarDoubleComplex.
And, we can just do an implementation that use the complex::real and complex::imag virtual member functions.
In fact, if you look at all operators of all complex numbers you will see that there will be many conversions from float to double and polar to scalar representation or scalar to polar representation. And writing x+y is not the same as writing y+x if x and y are not using the same representation.
In fact, it is preferable to use only one representation for all complex numbers (or at least don't mix representations).
But, now consider extending the standard complex class.
Why can we extends this class?
Not to modify the representation, because it is already defined!
But, we can add new member functions, and no fields. But for that we should not derive, but just add non-member functions.
We can derive to add caching informations.
That is a true concrete application of derivation.
For example, we want to cache the magnitude of the complex number, because we write an application that needs many call to the norm functions.
The ComplexWithMagnitudeCached class is just deriving from complex, and redefines the norm class, and adds a double magnitude; field whose value is initiated to +NAN.
So, we can think that the best is to declare all functions as being virtual in the complex class, because derived classes can derive from them.
But, you don't need that!
You can redefine norm in the derived class without needing this function be virtual.
And it is really absolutely sure that you will not use this class polymorphically with a pointer that can points either to complex or to ComplexWithMagnitudeCached. You will use ComplexWithMagnitudeCached everywhere needed, and complex everywhere you don't need that the norm be computed fastly, and write a conversion operator from complex to ComplexWithMagnitudeCached, but you will never have polymorphic pointers. Moreover if we manipulate polymorphic pointers we cannot know if the norm function executes fastly or not. It is far better to don't use polymorphism here.
Moreover a ComplexWithMagnitudeCached can be used perfectly with functions (of a foreign library for example) that use pointers or references (or values) to the complex class.
The only thing you must know, is that the norm function of the base class will be called instead of the optimized function of the derived class. What is not really a problem, because this function do its work correctly. Yes, the optimization will not be used, but if the library do much computing that really needs to compute fastly magnitudes, be sure that there is no problem, because this library probably uses its own optimized representation in internal computings (if it is an optimal library).
Moreover in many situations we use complex values (and not pointers nor references). And in that case polymorphism is totally useless.
And polymorphism is really not cheap for complex numbers, because:
1) It needs a Vitual Table pointer which uses 4 bytes, and for alignment reasons the compiler (or the programmer) will align on 8 bytes boundary the structure, so it uses 24 bytes instead of 16 bytes.
2) since most of the frequently used functions of complex are very simple, making them virtual reduce by a not-negligible factor the speed (even if this factor is not exorbitant).
The memory space factor is probably the more important.
The complex class can be derived, but is cannot be polymorphically used, what does not means that its derived classes can't be polymorphically used.
If you need to use polymorphically a whole complex number hierarchy, you can. You just need to derive the base class of your hierarchy from complex, and to redefines as virtual, exactly all the functions you need to be virtual (including very probably the destructor).
So, by not putting any virtual functions in the complex class, the flexibility is maximized. Because you can choose, when you derive from it, to not define any virtual function (the most probable), or to defines exactly the functions you need as virtual (probably totally new functions, and probably not already existing functions in the complex class).
You must know that if an exisiting hierarchy is not polymorphic that don't means that all derived classes cannot be polymorphic, and that don't means also that the already existing hierarchy is bad. It just means that the already existing hierarchy has no need to be polymorphically used. Because, for example, all functions don't need to be redefined polymorphically. They are defined optimally and do exactly the job they need to do.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|