|
Polymorphism & Virtual Functions |
|
|
|
|
Written by Hemanshu Patel
|
|
Wednesday, 24 October 2007 |
|
Page 7 of 10 Overloading & overriding
In Chapter 14, you saw that redefining an overloaded function in the base class hides all of the other base-class versions of that function. When virtual functions are involved the behavior is a little different. Consider a modified version of the NameHiding.cpp example from Chapter 14: //: C15:NameHiding2.cpp // Virtual functions restrict overloading #include <iostream> #include <string> using namespace std;
class Base { public: virtual int f() const { cout << "Base::f()\n"; return 1; } virtual void f(string) const {} virtual void g() const {} };
class Derived1 : public Base { public: void g() const {} };
class Derived2 : public Base { public: // Overriding a virtual function: int f() const { cout << "Derived2::f()\n"; return 2; } };
class Derived3 : public Base { public: // Cannot change return type: //! void f() const{ cout << "Derived3::f()\n";} };
class Derived4 : public Base { public: // Change argument list: int f(int) const { cout << "Derived4::f()\n"; return 4; } };
int main() { string s("hello"); Derived1 d1; int x = d1.f(); d1.f(s); Derived2 d2; x = d2.f(); //! d2.f(s); // string version hidden Derived4 d4; x = d4.f(1); //! x = d4.f(); // f() version hidden //! d4.f(s); // string version hidden Base& br = d4; // Upcast //! br.f(1); // Derived version unavailable br.f(); // Base version available br.f(s); // Base version abailable } ///:~ The first thing to notice is that in Derived3, the compiler will not allow you to change the return type of an overridden function (it will allow it if f( ) is not virtual). This is an important restriction because the compiler must guarantee that you can polymorphically call the function through the base class, and if the base class is expecting an int to be returned from f( ), then the derived-class version of f( ) must keep that contract or else things will break. The rule shown in Chapter 14 still works: if you override one of the overloaded member functions in the base class, the other overloaded versions become hidden in the derived class. In main( ) the code that tests Derived4 shows that this happens even if the new version of f( ) isn’t actually overriding an existing virtual function interface – both of the base-class versions of f( ) are hidden by f(int). However, if you upcast d4 to Base, then only the base-class versions are available (because that’s what the base-class contract promises) and the derived-class version is not available (because it isn’t specified in the base class). Variant return type The Derived3 class above suggests that you cannot modify the return type of a virtual function during overriding. This is generally true, but there is a special case in which you can slightly modify the return type. If you’re returning a pointer or a reference to a base class, then the overridden version of the function may return a pointer or reference to a class derived from what the base returns. For example: //: C15:VariantReturn.cpp // Returning a pointer or reference to a derived // type during ovverriding #include <iostream> #include <string> using namespace std;
class PetFood { public: virtual string foodType() const = 0; };
class Pet { public: virtual string type() const = 0; virtual PetFood* eats() = 0; };
class Bird : public Pet { public: string type() const { return "Bird"; } class BirdFood : public PetFood { public: string foodType() const { return "Bird food"; } }; // Upcast to base type: PetFood* eats() { return &bf; } private: BirdFood bf; };
class Cat : public Pet { public: string type() const { return "Cat"; } class CatFood : public PetFood { public: string foodType() const { return "Birds"; } }; // Return exact type instead: CatFood* eats() { return &cf; } private: CatFood cf; };
int main() { Bird b; Cat c; Pet* p[] = { &b, &c, }; for(int i = 0; i < sizeof p / sizeof *p; i++) cout << p[i]->type() << " eats " << p[i]->eats()->foodType() << endl; // Can return the exact type: Cat::CatFood* cf = c.eats(); Bird::BirdFood* bf; // Cannot return the exact type: //! bf = b.eats(); // Must downcast: bf = dynamic_cast<Bird::BirdFood*>(b.eats()); } ///:~ The Pet::eats( ) member function returns a pointer to a PetFood. In Bird, this member function is overloaded exactly as in the base class, including the return type. That is, Bird::eats( ) upcasts the BirdFood to a PetFood. But in Cat, the return type of eats( ) is a pointer to CatFood, a type derived from PetFood. The fact that the return type is inherited from the return type of the base-class function is the only reason this compiles. That way, the contract is still fulfilled; eats( ) always returns a PetFood pointer. If you think polymorphically, this doesn’t seem necessary. Why not just upcast all the return types to PetFood*, just as Bird::eats( ) did? This is typically a good solution, but at the end of main( ), you see the difference: Cat::eats( ) can return the exact type of PetFood, whereas the return value of Bird::eats( ) must be downcast to the exact type. So being able to return the exact type is a little more general, and doesn’t lose the specific type information by automatically upcasting. However, returning the base type will generally solve your problems so this is a rather specialized feature.
|
|
Last Updated ( Wednesday, 24 October 2007 )
|