Virtual methods
One very important feature of every object oriented language is the possibility to redefine methods in derived class. In C++ this technique is implemented using the virtual keyword for methods. This keyword ensures that always the correct version of a method is invoked, even if invocation is done by a base class. Consider the following example for details.
class BaseClass {
public:
void f();
virtual void g();
};
class DerivedClass : public BaseClass {
public:
void f();
void g();
};
...
BaseClass* b,c;
DerivedClass* d;
b = new BaseClass();
c = d = new DerivedClass();
b->f(); // Will call BaseClass::f
b->g(); // Will call BaseClass::g
c->f(); // Will call BaseClass::f
c->g(); // Will call DerivedClass::g
d->f(); // Will call DerivedClass::f
d->g(); // Will call DerivedClass::g
|
If you still want BaseClass's implementation of method g with an object of class DerivedClass, this is still possible by using an explicit method resolution:
d->g(); // Will call DerivedClass::g d->BaseClass::g(); // Will call BaseClass::g |
This is especially useful if you want to call the original implementation of method g in the derived implementation of class DerivedClass, like in the following code snippet:
void DerivedClass::g() {
BaseClass::g(); // Call original implementation
... // Do other stuff here
};
|
Although virtual methods are a very powerful tool, you shall not make all methods virtual for the following two reasons: A virtual method invocation is slightly more expensive (in terms of computation time) than a non-virtual call and (this is the more important reason) virtuality is part of the interface of a class, that means making a method virtual changed the interface and semantics of a class. There are many situations where a redefinition of a method in a derived class does not make sense or even is not desired at all. In these case you must not use the virtual keyword.
| Rule 47. Virtual methods are to be protected. |
On the one side, virtuality is part of the interface of a class and on the other side it is part of the implementation of a class. Virtual methods should never called directly (except for derived classes which might need to call the original implementation), only through small wrappers. This also enables a check the validity of the parameters, which might make sense, as in many cases classes with virtual methods are meant to be derived by the user who forgets to check for valid parameters.
I found it to be very convenient to add the prefix on
in front of all virtual methods as a reminder and signal that this
is indeed a virtual methods. Then you can easily add an inline
function as a wrapper without the prefix, which helps to emphasise
the relation of the wrapper to the virtual function and vice versa.
Consider the following example:
class File {
public:
inline unsigned int read(char* ,unsigned int );
inline unsigned int write(const char* ,unsigned int );
protected:
virtual unsigned int onRead(char* ,unsigned int ) = 0;
virtual unsigned int onWrite(const char* ,unsigned int ) = 0;
};
inline unsigned int File::read(char* buffer, unsigned int size) {
// First check validity of parameter
ASSERT(buffer != NULL)
// Then call virtual method
return onRead(buffer, size)
}
inline unsigned int File::write(const char* buffer, unsigned int size) {
// First check validity of parameter
ASSERT(buffer != NULL)
// Then call virtual method
return onWrite(buffer, size)
}
|
Note that in this case the virtual methods are not defined at all in the class File, it is an abstract base class and has to be derived in order to be used. This class is merely a common interface for different objects that provide read and write capabilities (for example normal files, network connections, database files etc.).
| Rule 48. struct's never ever contain virtual methods. |
struct's are often used for a simple aggregation of objects and as such they should not expose complex behaviour as is implied by the usage of virtual methods. Plus in many cases struct's are used in conjunction with external files and they have to represent a certain memory layout. As long as no virtual methods are used, the exact layout of a struct is rather predictable, but as soon as you use virtual methods this property gets lost because the compiler has to store additional information inside the object in order to correctly resolve virtual method invocations.
Kaya Memisoglu 2005-01-06
