CS 70

Dynamic Dispatch

For a function like

void engageWith(Animal& animal) {
    animal.speak();
    animal.feed();
}

even though the code says that animal is an Animal, we want our program to figure out that the animal is actually a derived class like Cow or Raptor and call the right speak() member function for that class. In other words, not the static type we saw at compile time, but the dynamic type we actually have for that object at runtime.

  • Hedgehog speaking

    Why have we never had to deal with this before?

  • LHS Cow speaking

    These issues arise precisely because of subtype polymorphism. We've never had derived classes (or subtypes) before.


The terminology for what we want is dynamic dispatch. “Dispatch” is just “what member function to call” and “dynamic” is because we want it to be determined based on runtime information, not compile-time information.

  • RHS Cow speaking

    In some languages, such as Java and Python, dynamic dispatch is the default behavior.

  • LHS Cow speaking

    Specifically, languages that say that they are “object oriented” default to this behavior, as that's how most people expect objects to work.

  • Horse speaking

    Woah! So C++ isn't object oriented?

  • LHS Cow speaking

    In C++, we can get dynamic dispatch if we ask for it. So we can write object-oriented code in C++. Technically, if we don't ask for dynamic dispatch but we do use classes, our code is merely “object based”.

  • Hedgehog speaking

    Ugh. Why won't C++ ever just do the right thing without being told??

  • Bjarne speaking

    Zero overhead! Providing dynamic dispatch requires (a little bit of) overhead. And C++ won't make us pay for that feature unless we ask for it.

  • Cat speaking

    So how do we ask for it?

Dynamic Dispatch in C++

In C++, a base class can specify exactly which member function(s) will use dynamic dispatch by prefixing that member function with the virtual keyword, as in

virtual void speak() const;

When a function is marked virtual in a base class, it's automatically also virtual in the derived classes. If/when you override that function in a derived class, you should add the keyword override at the end to explicitly say you are providing a new (overriding) implementation. If you say override for a member function that wasn't declared virtual in the base class, you'll get a compiler error alerting you to your mistake.

  • Goat speaking

    Why is it virtual and not dynamic? Surely dynamic would make more sense because it's dynamic dispatch?

  • Bjarne speaking

Here's what our Animal, Cow, and Raptor classes look like with dynamic dispatch.

Animal with Dynamic Dispatch

class Animal {
 public:
    explicit Animal(std::string name, size_t hunger = 0);
    virtual ~Animal() = default;
    virtual void speak() const;
    virtual void feed();

 protected:
    std::string name_;
    size_t hunger_;
};
Animal::Animal(std::string name, size_t hunger)
    : name_{name}, hunger_{hunger} {
    // Nothing (else) to do
}

void Animal::speak() const {
    // idk what it even means for a generic animal to speak
    std::cout << name_ << " makes nonspecific animal noises" << std::endl;
}

void Animal::feed() {
    std::cout << name_ << " eats" << std::endl;
    if (hunger_ > 0) {
        --hunger_;
    }
}
  • Horse speaking

    Hay! Why are you marking the destructor as virtual?

  • LHS Cow speaking

    The destructor is a pretty important function. If you want to make sure you always call the right function, we certainly want to always call the right destructor. Specifically, it's for when we have an Animal* on the heap that we're going to delete.

Cow with Dynamic Dispatch

class Cow : public Animal {
 public:
    explicit Cow(std::string name);
    ~Cow() override = default;
    void speak() const override;
    void party() const;

 private:
    double happiness_;
};
Cow::Cow(std::string name)
    : Animal{name, 3}, happiness_{7.5} {
    // Nothing (else) to do
}

void Cow::speak() const {
    std::cout << name_ << " says: Mooooo" << std::endl;
}

void Cow::party() const {
    std::cout << name_ << " parties!!" << std::endl;
}
  • Cat speaking

    Shouldn't party() be marked virtual, too, so that classes that derive from Cow (RanchCow, perhaps?) can party in their own special ways?

  • LHS Cow speaking

    Yes, that would probably have been smart. But by not making party() virtual we're showing that you can still use static dispatch for some functions.

Raptor with Dynamic Dispatch

class Raptor : public Animal {
 public:
    explicit Raptor(std::string name);
    ~Raptor() override = default;
    void speak() const override;

 private:
    double anger_;
};
Raptor::Raptor(std::string name)
    : Animal{name, 42}, anger_{11} {
    // Nothing (else) to do
}

void Raptor::speak() const {
    std::cout << name_ << " says: Rawrrr" << std::endl;
}

Let's Try it Out!

Here's the revised code. Run it and see if it works the way we want now…

Do things look better now?

  • Dog speaking

    Yes!

  • LHS Cow speaking

    Mooooo!

  • Raptor speaking

    Rawrrr!

  • LHS Cow speaking

    Gyah! Where did you come from?

  • Goldblum speaking

    Life… uh… finds a way…?

(When logged in, completion status appears here.)