/* ** $Id: animals.cc 649 2007-03-30 00:46:20Z phf $ ** ** This is a small example of inheritance using various ** animals and their abilities to cutely fascinate us. ** ** This is *not* a complete record of *all* the things ** we did in lecture, instead it is a snapshot of the ** final versions. As always, feel free to ask about ** anything you don't understand. */ #include #include void bar() { std::cout << "==============================" << std::endl; } // Animal is an *abstract* class. It serves as (a) // a way of naming *all* kinds of animals and (b) // a way to define the abilities common to *all* // kinds of animals. A class is abstract in C++ if // it has at least one "pure virtual function" i.e. // those "=0" things. An abstract class can still // contain code and variables, but no instances of // the abstract class can be created. class Animal { public: // Note the "virtual" keyword. This is necessary in C++ // to ask for "dynamic binding" i.e. for deferring the // decision of what method is executed to runtime. If // we leave out "virtual" we get "static binding" and // the method executed is based simply on the static // type of the variable we call it on. We include one // example of this below, just for completeness. virtual void noise() =0; virtual void move() { std::cout << "[Animal::move] Yep, I am moving!" << std::endl; } void static_binding() { std::cout << "[Animal::static_binding]" << std::endl; } // Any class needs a constructor, so we add one. Note that // constructors are never virtual. Animal() {} // Any class with virtual methods needs a virtual destructor, // so we add one without code to avoid the warnings. The idea // is that destructor actions need to be carried out no matter // what class an actual object has, so they should be using // dynamic binding as well. virtual ~Animal() {} }; // Dogs inherit from Animal, which states several things. For // one, it means that "Dogs are a kind of Animal" in the sense // of classification. It also means that "Dogs can be used // where Animals are expected" which is called "substitutability" // in the technical literature. We *override* (not overload!) // the noise() method, which means that the class is "complete" // and not abstract anymore, so we can create Dog instances. // It's also called a "concrete class" for that reason. class Dog: public Animal { public: virtual void noise() { std::cout << "[Dog::noise] Woof!" << std::endl; } void static_binding() { std::cout << "[Dog::static_binding]" << std::endl; } Dog() {} }; // Cows inherit from Animal, which pretty much means the same // things we discussed for Dogs above. class Cow: public Animal { public: virtual void noise() { std::cout << "[Cow::noise] Moo!" << std::endl; } Cow() {} }; // Fishes inherit from Animal, once again with similar // consequences. However, here we override the move() // method as well; whereas Dog and Cow instances use // the move() defined in Animal, Fish instances will // use the move() defined here. Finally we also add // a *new* method, i.e. one that is not defined in a // base class already. This dive() method can only be // use on Fishes, not on any other Animals. class Fish: public Animal { public: virtual void noise() { std::cout << "[Fish::noise] Blub!" << std::endl; } virtual void move() { std::cout << "[Fish::move] Blub-Blub!" << std::endl; } virtual void dive() { std::cout << "[Fish::dive] Yep!" << std::endl; } Fish() {} }; // Just for fun we'll include two more subclasses for // dogs; this was not in the examples we did in lecture. class Terrier: public Dog { public: virtual void noise() { std::cout << "[Terrier::noise] Yelp!" << std::endl; } void static_binding() { std::cout << "[Terrier::static_binding]" << std::endl; } Terrier() {} }; class Bloodhound: public Dog { public: virtual void move() { std::cout << "[Bloodhound::move] Stomp!" << std::endl; } Bloodhound() {} }; // Here are some simple uses of these classes, nothing // fancy yet. void simple() { // First of all, we *cannot* declare a variable of // type Animal. If we try, we get an error message // since Animal is abstract and hence no objects // of class Animal can be created. Comment out the // following to see that. // Animal a; // a.noise(); // We can, however, declare variables of all the // concrete classes. Note what pieces of code are // *actually* called! Here we go: Dog d; d.noise(); // Dog::noise d.move(); // Animal::move (!) Cow c; c.noise(); // Cow::noise c.move(); // Animal::move (!) Fish f; f.noise(); // Fish::noise f.move(); // Fish::move (!) Terrier t; t.noise(); // Terrier::noise t.move(); // Animal::move (!!) Bloodhound b; b.noise(); // Dog::noise (!!) b.move(); // Bloodhound::move (!) // This all makes sense: If a method is overridden, it // gets called; if it's not overridden, the "closest" // method further up the inheritance hierachy gets // called instead, potentially all the way up to Animal // and the example of Terrier makes clear regarding the // move() method. // Now let's look at the one method we defined in the // whole mess that is *not* virtual: d.static_binding(); // Dog::static_binding c.static_binding(); // Animal::static_binding f.static_binding(); // Animal::static_binding t.static_binding(); // Terrier::static_binding b.static_binding(); // Dog::static_binding // What's going on here? We can really only explain // that in the next longer example... :-) } // Here are some polymorphic uses of these classes, now // we get slightly fancier. void polymorphic() { // All the concrete classes inherit (directly or indirectly) // from Animal. As we said before, that means that any concrete // class can be used whereever an Animal is expected. So we // should be able to assign, say, a Terrier instance to a Dog // variable. Let's try: Dog d; d.noise(); // Dog::noise d.move(); // Animal::move (!) Terrier t; d = t; // valid assignment d.noise(); // Dog::noise d.move(); // Animal::move (!) Bloodhound b; d = b; // valid assignment d.noise(); // Dog::noise d.move(); // Animal::move (!) // Oops, shouldn't those have called the methods defined in // the Terrier or Bloodhound classes? Actually it *can't* // work that way, check what we did. We defined Dog, Terrier, // and Bloodhound variables, we didn't define pointers or // references. That means the *content* of a Terrier or // Bloodhound is copied into the Dog variable. Since Terrier // and Bloodhound *could* contain more than can fit into a // Dog variable, the "excess stuff" is "sheared off" and lost. // So only the "Dog part" of a Terrier ends up in the Dog // variable. If we really want this to work, we need to switch // to pointers for example: Dog *dp = new Dog(); dp->noise(); // Dog::noise dp->move(); // Animal::move dp = new Terrier(); dp->noise(); // Terrier::noise (!) dp->move(); // Animal::move dp = new Bloodhound(); dp->noise(); // Dog::noise dp->move(); // Bloodhound::move (!) // Wow! Make sure you get this. We run the *same* identical // piece of code, the only this that changes is the object // we point to polymorphically. What happens is that the // right method for the object we point to gets called, not // the "right method" for "Dog" which is the static type of // the "dp" variable. This is what we mean by "dynamic // binding"! // Compare that to what happens for the method we defined // without the virtual keyword: Animal *ap = new Cow(); ap->static_binding(); // Animal::static_binding ap = new Dog(); ap->static_binding(); // Animal::static_binding ap = new Terrier(); ap->static_binding(); // Animal::static_binding dp = new Dog(); dp->static_binding(); // Dog::static_binding dp = new Terrier(); dp->static_binding(); // Dog::static_binding Terrier *tp = new Terrier(); tp->static_binding(); // Terrier::static_binding // All methods resolve to the *static* type of the pointer, // not the dynamic type (i.e. the type of the object we // point to). That's the difference between having the // "virtual" keyword or not. } // We can make this even more fascinating by writing a // function that uses it's argument polymorphically. // As above, we need to pass a pointer or reference, // we can't pass "Animal a" since then we get things // "sheared off" again. So here's a function that can // be used to "tease" *any* animal. It's one function // that can be independently compiled and which will // work with *any* Animal subclass, even those not yet // written when tease() was compiled. Pretty cool... void tease( Animal &a ) { a.noise(); a.move(); } // Some examples for teasing animals. void do_tease() { Dog d; Cow c; Fish f; tease( d ); tease( c ); tease( f ); } // Here's an even more complex example, teasing all // animals of a zoo. void zoo_tease() { // A zoo is a bunch of *general* animals... std::vector zoo; // ...which can be filled with all kinds of animals... zoo.push_back( new Fish() ); zoo.push_back( new Fish() ); zoo.push_back( new Dog() ); zoo.push_back( new Cow() ); zoo.push_back( new Dog() ); zoo.push_back( new Terrier() ); zoo.push_back( new Bloodhound() ); zoo.push_back( new Cow() ); // ...and we can loop through it doing the right thing thanks to polymorphism! for (unsigned i = 0; i < zoo.size(); i++) { zoo.at(i)->noise(); zoo.at(i)->move(); } } // Finally, note that the dive() method can *only* be // used for Fishes, not anything else. void diving() { Fish f; f.dive(); // Dog d; // d.dive(); } // Bla. int main() { bar(); simple(); bar(); polymorphic(); bar(); do_tease(); bar(); zoo_tease(); bar(); }