CS 70

Pure Virtual Member Functions

  • Cat speaking

    I was saying I didn't see the point of being able to make vanilla Animal objects—we only want to make objects of classes derived from Animal.

  • Hedgehog speaking

    And, now that you mention it, the Animal::speak() function was kinda creepy, with its “nonspecific animal noises”. Can that go away?

  • LHS Cow speaking

    You both get your wish.

In C++, it's possible to declare a function as a pure virtual function, which means that we declare it in the base class, but say that the base class doesn't implement that function. All (concrete) derived classes must override it and provide their own implementation.

When we use this approach, it also makes the base class an abstract class, which means that we can't make any objects of that base class, only objects that derive from it.

Declaring a Pure Virtual Function

We use the syntax

virtual void speak() const = 0;

The = 0; means “make this function pure virtual”.

  • Goat speaking

    Okay, I'd managed to swallow saying things like = delete; to not have a function at all, but = 0;? Really? Couldn't we have had = abstract; or something?

  • Bjarne speaking

    When I was first adding this feature, I wasn't sure if people would be up in arms if I added a new keyword to the language. But I figured I could get away with = 0;. And it reminded me of how—internally—the system stores a null pointer in the virtual-function table that is used to implement dynamic dispatch.

  • Duck speaking

    What's a virtual-function table?

  • Bjarne speaking

    Each class has a vtable—a table of pointers to each of its virtual functions, and each object points to its class's vtable. That's how dynamic dispatch works behind the scenes: it finds the vtable, goes to a specific offset to find the function it needs, and calls that function.

  • LHS Cow speaking

    But the C++ standard doesn't even acknowledge that dynamic dispatch is implemented using vtables! You can't really expect other people to be thinking about those arcane C++ implementation details, can you?

  • Bjarne speaking

Abstract Classes as Interfaces

  • Duck speaking

    In Java, they also have interfaces. Does C++ have that?

  • LHS Cow speaking

    Basically, yes.

If we make a base class that has

  • No data members, and
  • All member functions declared as pure virtual,

that class is a special kind of abstract base class: an interface class. It specifies what the class needs to be able to do, without saying anything about how.

Let's look at an example:

class Merrymaker {
 public:
    virtual ~Merrymaker() = default;
    virtual void party() const = 0;
};

We can adjust our Cow class to say it is also a Merrymaker as follows:

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

 private:
    double happiness_;
};
  • Duck speaking

    So you can inherit from multiple classes?

  • Bjarne speaking

    Yes! As many as you like!

  • LHS Cow speaking

    But in modern C++, if you inherit from multiple classes, the extra ones should all be interface classes. Experience has shown that people get pretty confused otherwise.

  • Bjarne speaking

    But that's just a programming practice. You can do as you please.

Let's also make a class that isn't an Animal class but does support the Merrymaker interface:

class PrimeMinister : public Merrymaker {
 public:
    explicit PrimeMinister(std::string name);
    void party() const override;

 private:
    std::string name_;
    double charisma_ = 1.0;
};

PrimeMinister::PrimeMinister(std::string name) : name_{name} {
    // Nothing (else) to do
}

void PrimeMinister::party() const {
    std::cout << name_ << " parties!! (but claims it's all work)"
              << std::endl;
}

And here's some code that uses our Merrymaker interface:

std::vector<Merrymaker*> partyGoers;

partyGoers.push_back(new Cow{"Bessie"});
partyGoers.push_back(new PrimeMinister{"Boris Johnson"});
partyGoers.push_back(new PrimeMinister{"Justin Trudeau"});
partyGoers.push_back(new Cow{"Mabel"});

for (Merrymaker* partyGoer : partyGoers) {
    partyGoer->party();
}

while (!partyGoers.empty()) {
    delete partyGoers.back();
    partyGoers.pop_back();
}

Try out this code:

Look over the code. Run it. What do you think about pure virtual functions, abstract base classes, and interface classes?

  • Cat speaking

    I noticed that the code for engageWith() had an extra bit to see if it was really a Cow using dynamic_cast.

  • LHS Cow speaking

    That's right. These are similar to the casts you often have to do when writing object-oriented code in Java, to try to turn an object that is only known to be a base class (like Animal*) into a specific derived class (like Cow*).

  • Rabbit speaking

    If the object isn't the one you hoped for, the cast fails (returning a null pointer for pointers, or throwing std::bad_cast for references). You can only use dynamic_cast on a object that has at least one virtual function. Casting to a Cow is known as a downcast, but you could change the code to try to cast to a Merrymaker instead—that would be a sidecast.

  • LHS Cow speaking

    We really don't need to dive down the dynamic_cast rabbit hole!

  • Goat speaking

    Will I ever actually use any of this stuff?

  • LHS Cow speaking

    These ideas are in a lot of languages, including Java. In C++, we're just more explicit about what's going on.

  • Rabbit speaking

    If you program in Swift, they call interfaces protocols, and if you code in Haskell, they're called type classes. The details vary, but the core ideas are fundamentally the same.

  • Hedgehog speaking

    So, I guess we could say that C++ has all the object-oriented stuff you'd ever want. But if you don't want or need to use it, that's a valid choice, too. Do whatever feels simplest?

  • LHS Cow speaking

    That'll do it!

(When logged in, completion status appears here.)