|
Page 5 of 6 virtual Methods virtual methods are an essential part of designing a class hierarchy and subclassing classes from a toolkit. The concept is relatively simple, but often misunderstood. Specifically it determines the behavior of overridden methods in certain contexts. By placing the keyword virtual before a method declaration it says that when referring to an instance of a superclass by a pointer or reference to a base class that the correct implementation should be resolved at run time and that the "highest level" implementation should be used. Again, this should be more clear with an example: class Foo { public: void f() { std::cout << "Foo::f()" << std::endl; } virtual void g() { std::cout << "Foo::g()" << std::endl; } }
class Bar : public Foo { public: void f() { std::cout << "Bar::f()" << std::endl; } virtual void g() { std::cout << "Bar::g()" << std::endl; } }
int main() { Foo foo; Bar bar;
Foo *baz = &bar; Bar *quux = &bar;
foo.f(); // "Foo::f()" foo.g(); // "Foo::g()"
bar.f(); // "Bar::f()" bar.g(); // "Bar::g()"
// So far everything we would expect...
baz->f(); // "Foo::f()" baz->g(); // "Bar::g()"
quux->f(); // "Bar::f()" quux->g(); // "Bar::g()"
return 0; } Our first calls to f() and g() on the two objects are straightforward. However things get interesting with our baz pointer which is a pointer to the Foo type. f() is not virtual and as such a call to f() will always invoke the implementation associated with the pointer type -- in this case the implementation from Foo. "Pure" Virtual Methods There is one additional interesting possiblity -- sometimes we don't want to provide an implementation of our function at all, but want to require people subclassing our class to be required to provide an implementation on their own. This is the case for "pure" virtuals. To indicate a "pure" virtual method instead of an implementation we simply add an "= 0" after the function declaration. Again -- an example: class Widget { public: virtual void paint() = 0; };
class Button : public Widget { public: virtual void paint() { // do some stuff to draw a button } }; Because paint() is a pure virtual method in the Widget class we are required to provide an implementation in all subclasses. If we don't the compiler will give us an error at build time. This is helpful for providing interfaces -- things that we expect from all of the objects based on a certain hierarchy, but when we want to ignore the implementation details. So why is this useful? Let's take our example from above where we had a pure virtual for painting. There are a lot of cases where we want to be able to do things with widgets without worrying about what kind of widget it is. Painting is an easy example. Imagine that we have something in our application that repaints widgets when they become active. It would just work with pointers to widgets -- i.e. Widget *activeWidget() const might be a possible function signature. So we might do something like: Widget *w = window->activeWidget(); w->paint(); We want to actually call the appropriate paint method for the "real" widget type -- not Widget::paint() (which is a "pure" virtual and will cause the program to crash if called). By using a virtual method we insure that the method implementation for our subclass -- Button::paint() in this case -- will be called. Note: While it is not required to use the virtual keyword in our subclass implementations (since if the base class implementation is virtual all subclass implementations will be virtual) it is still good style to do so.
|