Example: The CS 70 Petting Zoo
Today we're going to implement a petting zoo for CS 70!
Our petting zoo will have
Cow
s, 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
Raptor
s!
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 ofAnimal
s. Not really a big deal here, but if we had more kinds ofAnimal
s 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 eitherstd::vector<Cow>
orstd::vector<Raptor>
; we can't just saystd::vector<Cow or Raptor>
! - People can call
engageWith()
with types likeint
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.
Actually, there are ways to put heterogenous values in a
std::vector
; for example,std::any
andstd::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. Cow
s and Raptor
s 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, theAnimal
class. That's an improvement.What does
protected
mean in theAnimal
class? It sounds so nice and cozy.Why does it say
: public Animal
in theCow
andRaptor
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 asprotected
, but it can't access any member functions that areprivate
. - Is a subtype of the base class (e.g., we can use a
Cow
orRaptor
anywhere we need anAnimal
).
So when you write a base class, you probably want to declare things
protected
rather thanprivate
?If you want derived classes to be able to use them, yes. If we hadn't declared
name_
asprotected
, ourCow
s 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 theCow
constructor?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.)