CS 70

Example: The CS 70 Petting Zoo

  • LHS Cow speaking

    Today we're going to implement a petting zoo for CS 70!

  • RHS Cow speaking

    Our petting zoo will have Cows, obviously,

class Cow {
 public:
    explicit Cow(std::string name);
    void speak() const;
    void feed();
    void party() const;

 private:
    std::string name_;
    double happiness_ = 7.5;
    size_t hunger_ = 3;
};
Cow::Cow(std::string name) : name_{name} {
    // Nothing (else) to do
}

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

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

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

    And Raptors!

class Raptor {
 public:
    explicit Raptor(std::string name);
    void speak() const;
    void feed();

 private:
    std::string name_;
    double anger_ = 11;
    size_t hunger_ = 42;
};
Raptor::Raptor(std::string name) : name_{name} {
    // Nothing (else) to do
}

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

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

    Raptors??

  • LHS Cow speaking

    Sure! What could go wrong?

  • Goldblum speaking

    Your programmers were so preoccupied with whether they could, they didn't stop to think if they should.

How Do We Make This Work?

  • LHS Cow speaking

    Anyway. Our (certainly lucrative and successful, what could go wrong?) business model depends on implementing this function:

void engageWith(????? creature) {
    creature.feed();
    creature.speak();
}

That way, we can try out our petting zoo with two animals like

int main() {
    Cow bessie("Bessie");
    Raptor peri("Peri");

    std::cout << "# Petting the Cow:" << std::endl;
    engageWith(bessie);

    std::cout << "\n# Petting the Raptor:" << std::endl;
    engageWith(peri);

    return 0;
}
  • Dog speaking

    Ooh, we could make this work with templates!

  • LHS Cow speaking

    Yes…

We can write a function template like

template <typename T>
void engageWith(T creature) {
    creature.feed();
    creature.speak();
}

Here's all the code we've seen so far where you can run it.

But…

  • There will be as many engageWith() template instantiations as there are different kinds of Animals. Not really a big deal here, but if we had more kinds of Animals and there were more functions or the functions were larger, memory usage might be significant.
  • If we wanted to have std::vector holding all our animals, it wouldn't work, as we have to pick either std::vector<Cow> or std::vector<Raptor>; we can't just say std::vector<Cow or Raptor>!
  • People can call engageWith() with types like int that can't .feed() or .speak(). They'll get a compiler error, but only after the compiler has started trying to instantiate the template and sees the calls don't work.
  • Rabbit speaking

    Actually, there are ways to put heterogenous values in a std::vector; for example, std::any and std::variant.

  • LHS Cow speaking

    Hush! We don't need to go down that rabbit hole! Those recent C++ additions aren't the best solution here anyway.

Sometimes there's a natural hierarchy. Cows and Raptors are both animals, and there's some copy-and-pasted code (e.g., the feed() member functions are identical!) so it makes sense to have them both be derived from an Animal base class.

An Animal Base Class

class Animal {
 public:
    explicit Animal(std::string name, size_t hunger = 0);
    void speak() const;
    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_;
    }
}

Cow now derives from Animal

class Cow : public Animal {
 public:
    explicit Cow(std::string name);
    void speak() const;
    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;
}

Raptor now derives from Animal

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

 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;
}
  • RHS Cow speaking

    BTW, we've thrown the code from the header file and implementation file together here. That's not a good idea in general, but for this page it puts all the code in one place to make it easier to follow. When you get to see it in OnlineGDB, it'll be split up properly.

What do you notice that's new in this code? What questions do you have about it?

  • LHS Cow speaking

    Let's see what answer the animal friends came up with…

  • Cat speaking

    The copy-and-pasted code for feed() is now in one place, the Animal class. That's an improvement.

  • Hedgehog speaking

    What does protected mean in the Animal class? It sounds so nice and cozy.

  • Duck speaking

    Why does it say : public Animal in the Cow and Raptor classes?

Public Inheritance

Public inheritance is like the inheritance you know from Java. The derived class

  • Gets the same data members that the base class had (but it can only “see” the ones declared as protected).
  • Gets all the member functions that the base class had. The public ones are still public, and the derived class can also access any member function declared as protected, but it can't access any member functions that are private.
  • Is a subtype of the base class (e.g., we can use a Cow or Raptor anywhere we need an Animal).
  • Cat speaking

    So when you write a base class, you probably want to declare things protected rather than private?

  • LHS Cow speaking

    If you want derived classes to be able to use them, yes. If we hadn't declared name_ as protected, our Cows wouldn't be able to know their own names.

  • Duck speaking

    So what's private inheritance? Is that a thing?

  • Bjarne speaking

    Yes, it's where we inherit but keep our inheritance a secret. We don't make public parts of the base class public, and we don't create a subtype relationship.

  • LHS Cow speaking

    We don't need to know about private inheritance in CS 70. It's not used all that often. There are some niche circumstances where private inheritance makes things easier.

  • Horse speaking

    Hay! What's the : Animal{name, 3} bit in the Cow constructor?

  • LHS Cow speaking

    In the member-initialization list, we can specify how to construct the base-class part of the object.

  • Dog speaking

    Why haven't you given us a link to run this code?

  • LHS Cow speaking

    It's coming…

(When logged in, completion status appears here.)