Example: The CS 70 Petting Zoo
Today we're going to implement a petting zoo for CS 70!
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;
}
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_;
}
}
Raptors??
Sure! What could go wrong?
Your programmers were so preoccupied with whether they could, they didn't stop to think if they should.
How Do We Make This Work?
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;
}
Ooh, we could make this work with templates!
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 ofAnimals. Not really a big deal here, but if we had more kinds ofAnimals and there were more functions or the functions were larger, memory usage might be significant. - If we wanted to have
std::vectorholding all our animals, it wouldn't work, as we have to pick eitherstd::vector<Cow>orstd::vector<Raptor>; we can't just saystd::vector<Cow or Raptor>! - People can call
engageWith()with types likeintthat 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.
Actually, there are ways to put heterogenous values in a
std::vector; for example,std::anyandstd::variant.
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;
}
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.
Let's see what answer the animal friends came up with…
The copy-and-pasted code for
feed()is now in one place, theAnimalclass. That's an improvement.
What does
protectedmean in theAnimalclass? It sounds so nice and cozy.
Why does it say
: public Animalin theCowandRaptorclasses?
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
publicones are still public, and the derived class can also access any member function declared asprotected, but it can't access any member functions that areprivate. - Is a subtype of the base class (e.g., we can use a
CoworRaptoranywhere we need anAnimal).
So when you write a base class, you probably want to declare things
protectedrather thanprivate?
If you want derived classes to be able to use them, yes. If we hadn't declared
name_asprotected, ourCows wouldn't be able to know their own names.
So what's private inheritance? Is that a thing?
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.
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.
Hay! What's the
: Animal{name, 3}bit in theCowconstructor?
In the member-initialization list, we can specify how to construct the base-class part of the object.
Why haven't you given us a link to run this code?
It's coming…
(When logged in, completion status appears here.)