Start learning free

LESSON 186 OF 343 · OBJECT-ORIENTED PROGRAMMING

OOP Capstone in C++: An Animal Hierarchy with Polymorphism

Lesson Read the full explanation

TL ; DR:

Put it all together: an abstract parent class, three concrete subclasses, and a polymorphic loop that dispatches by runtime type. This is the shape of nearly every real OOP design.


class Animal {
public:
    string name;
    Animal(string name) { this->name = name; }
    virtual string sound() = 0;   // each subclass picks
    virtual ~Animal() = default;
    void speak() {                 // shared by all
        cout << name << ": " << sound() << endl;
    }
};
// Dog, Cat, Bird each override sound
// vector<Animal*> zoo = { new Dog("Rex"), ... };
// for (Animal* a : zoo) a->speak();


Full lesson:

This problem stitches together everything you've learned in this chapter:

  1. Inheritance — three concrete classes share a common parent.
  2. Constructors — each subclass forwards a name to the parent.
  3. Abstract methods — the parent declares sound() with no body; each child must provide one.
  4. Shared behavior — the parent has a concrete speak() that calls sound(); every subclass inherits it for free.
  5. Polymorphism — a collection typed as Animal holds a Dog, a Cat, and a Bird; speak() dispatches to the right sound() at runtime.

The shape of this code is the shape of nearly every real OOP design you'll see — a small contract at the top (the abstract parent), concrete variants below, and the rest of the program written against the abstract type. New variants get added without touching the consumer code.


Your task

The abstract Animal class is already defined, with a concrete speak() that calls the abstract sound(). Add three subclasses — Dog (returns "Woof"), Cat (returns "Meow"), and Bird (returns "Tweet"). Then in MainFunction / main, build a polymorphic collection holding one of each (Rex, Whiskers, Tweety in that order) and loop through it calling speak().

Expected output:

Rex: Woof
Whiskers: Meow
Tweety: Tweet
STEP 1 OF 2
    Ctrl + Enter to check

    C++ · OBJECT-ORIENTED PROGRAMMING

    Oop Capstone in C++, explained

    What the capstone combines

    This lesson uses most of the chapter in one small program: an abstract parent class, three concrete subclasses, and one loop that works on all of them without knowing which is which. The structure is the usual shape of object-oriented code: a small contract at the top, concrete variants below it, and the rest of the program written against the contract.

    Five ideas appear in order:

    1. Inheritance. Dog, Cat and Bird all derive from Animal.
    2. Constructors. Each subclass receives a name and hands it to the parent constructor.
    3. A pure virtual function. Animal declares sound() with = 0, so every concrete subclass must supply its own.
    4. Shared behaviour. Animal::speak() has a body that calls sound(), so all subclasses get it for free.
    5. Polymorphism. A collection of Animal* holds different kinds of animals, and speak() reaches the right sound() at run time.

    The parent class

    The lesson provides Animal:

    class Animal {
    public:
        string name;
        Animal(string name) { this->name = name; }
        virtual string sound() = 0;
        virtual ~Animal() = default;
        void speak() {
            cout << name << ": " << sound() << endl;
        }
    };
    

    Notice that speak() is an ordinary function, but it calls sound(), which is virtual. Inside speak(), the call to sound() goes through the current object, and a virtual call is resolved by the object's real type. So speak() is written once and prints the right sound for every subclass.

    Because sound() is pure virtual, Animal is abstract and Animal a("x"); doesn't compile. The virtual destructor matters because the program deletes objects through Animal* pointers.

    Step by step

    Step 1: three subclasses

    Each subclass needs three things:

    • public inheritance, written : public Animal;
    • a constructor that passes the name to the parent in its initializer list, Dog(string name) : Animal(name) {}. The parent has no default constructor, so a subclass that doesn't call Animal(name) explicitly doesn't compile;
    • an override of sound() that returns its text, marked override so the compiler checks that it matches the parent's declaration.

    If a subclass skips sound(), it stays abstract and new Dog("Rex") is an error.

    Step 2: the polymorphic collection

    In mainFunction, build a vector<Animal*> holding a new Dog("Rex"), a new Cat("Whiskers") and a new Bird("Tweety"), in that order. Then loop over it and call speak() on each pointer with ->. A second loop deletes every object, because each one was created with new.

    The solution in C++

    class Animal {
    public:
        string name;
        Animal(string name) { this->name = name; }
        virtual string sound() = 0;
        virtual ~Animal() = default;
        void speak() {
            cout << name << ": " << sound() << endl;
        }
    };
    
    class Dog : public Animal {
    public:
        Dog(string name) : Animal(name) {}
        string sound() override { return "Woof"; }
    };
    
    class Cat : public Animal {
    public:
        Cat(string name) : Animal(name) {}
        string sound() override { return "Meow"; }
    };
    
    class Bird : public Animal {
    public:
        Bird(string name) : Animal(name) {}
        string sound() override { return "Tweet"; }
    };
    
    class Solution {
    public:
        void mainFunction() {
            vector<Animal*> animals = { new Dog("Rex"), new Cat("Whiskers"), new Bird("Tweety") };
            for (Animal* a : animals) {
                a->speak();
            }
            for (Animal* a : animals) delete a;
        }
    };
    

    It prints:

    Rex: Woof
    Whiskers: Meow
    Tweety: Tweet
    

    C++ details worth knowing

    • Why pointers. A vector<Animal> couldn't even compile here, because Animal is abstract. With a concrete base class it would compile but cause slicing: copying a Dog into an Animal keeps only the Animal part, and the dog's behaviour is lost. Pointers (or references) keep the real object intact.
    • Initializer list. The part after the colon runs before the constructor body. Base-class constructors are called there, which is the only place to pass arguments to them.
    • Ownership. The vector holds raw pointers, so it does not free the objects. Each new needs a matching delete. Deleting through Animal* is safe only because the destructor is virtual.
    • Smart pointers. In larger programs, vector<unique_ptr<Animal>> with make_unique<Dog>("Rex") frees the objects automatically and removes the second loop. The lesson uses raw pointers to show the mechanics.
    • Passing by value. A function taking Animal by value can't accept a Dog at all when Animal is abstract. Take a reference or pointer.

    Edge cases and common mistakes

    • Forgetting to override sound(). The subclass remains abstract, and creating it fails to compile.
    • Forgetting the initializer list. Dog(string name) {} tries to call Animal(), which doesn't exist.
    • Wrong order in the vector. The output follows the order of insertion, so Rex, Whiskers and Tweety must be created in that order.
    • Skipping the delete loop. The output looks correct, but the three objects are never freed.
    • A non-virtual destructor. Deleting a Dog through an Animal* then has undefined behaviour.
    • Misspelling in an override. string Sound() override fails to compile because it overrides nothing. Without override, the typo would create a new function and leave the class abstract with a less helpful error.

    Why this design scales

    Adding a Cow means writing one more subclass and one more new Cow("Daisy"). The speak function and the loop are untouched, because they only know about Animal. That is the benefit the capstone is meant to show.

    Key takeaways

    • An abstract parent declares the contract with pure virtual functions and can offer shared functions built on them.
    • Subclasses forward constructor arguments with an initializer list and mark their overrides with override.
    • Store base-class pointers (or references) to get polymorphism, and give the base a virtual destructor.
    • Every new needs a delete, or an owning smart pointer.
    Keep going with C++About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free