📚 Gaddis (Ch. 15.4)
Consider this situation:
Class BaseClass
defines methods x()
and y()
.
x()
calls y()
.
Class DerivedClass
inherits from BaseClass
and redefines method y()
.
An object D
of class DerivedClass
is created and method x()
is called.
When x()
is called, which y()
is used; the one defined in BaseClass
or the the redefined one in DerivedClass
?
#include <iostream>
class BaseClass{
public:
void x() {
std::cout << "BaseClass::x()\n";
y();
}
void y() {
std::cout << "BaseClass::y()\n";
}
};
class DerivedClass : public BaseClass {
public:
void y() {
std::cout << "DerivedClass::y()\n";
}
};
int main() {
DerivedClass d1;
d1.x(); // what is the output?
return 0;
}
Answer to the riddle:
The BASE class’s y()
method is called!
x()
in the base class’s point-of-view (at compile time), the only available y()
is its own.
x()
to the base class y()
at compile time… Once it’s done, it’s done.
A derived class can also be used as a base class.
This (of course) complicates issues like method redefining…
Virtual Method: a method in a base class that expects to be redefined in derived classes.
virtual
#include <iostream>
class BaseClass{
public:
void x() {
std::cout << "BaseClass::x()\n";
y();
}
virtual void y() { // NOTE: virtual method
std::cout << "BaseClass::y()\n";
}
};
class DerivedClass : public BaseClass {
public:
virtual void y() { // virtual because BaseClass::y() is...
std::cout << "DerivedClass::y()\n";
}
};
int main() {
DerivedClass d1;
d1.x(); // will work as desired now.
return 0;
}
A pointer of the base-class type may be “pointed” to a derived class object.
Remember the “is-a” relationship…
When the base class uses dynamic binding…
Polymorphism requires a pointer or reference.
#include <iostream>
class BaseClass{
public:
void x() {
std::cout << "BaseClass::x()\n";
y();
}
virtual void y() {
std::cout << "BaseClass::y()\n";
}
};
class DerivedClass : public BaseClass {
public:
virtual void y() {
std::cout << "DerivedClass::y()\n";
}
};
int main() {
BaseClass* d2 = new DerivedClass;
d2->x(); // will behave as DerivedClass
delete d2;
return 0;
}
Redefining: refers to statically-bound methods.
Overriding: refers to dynamically-bound methods.
Redefined methods do not exhibit polymorphic behavior
Overridden methods do exhibit polymorphic behavior.
virtual
Whenever you think a derived class might want to override a method!
virtual
!
virtual
.virtual
MethodsPure virtual
method: a method that is not implemented (at all) in the base class, and is thus required to be overridden in derived classes.
= 0
” tells the compiler “this is a pure virtual method—don’t expect an implementation”.Abstract base class: a base class that contains at least one pure virtual method.
Useful for factoring out common behavior from a family of objects.
You cannot instantiate an object from an abstract base class.
You can create pointers to abstract base classes (useful for polymorphic behavior).
Multiple Inheritance
Here is what it looks like in a class diagram:
Multiple Inheritance Class Diagram
class Student{ | class Faculty{
public: | public:
std::string name; | std::string name;
unsigned long id; | double salary;
}; | };
Notice the overlap - both classes need a name
(and maybe other things as well).
Good programming practice would say we should “factor out” the common code…
class Person{
public:
std::string name;
};
class Student : public Person{ | class Faculty : public Person{
public: | public:
unsigned long id; | double salary;
}; | };
Now, we have the common code collected in a base class Person
. Good!
But what if we want to add graduate assistants (GA’s)?
A GA is a student who also has some responsibilities similar to faculty. Could we use multiple inheritance?
class Person{
public:
std::string name;
};
class Student : public Person{ | class Faculty : public Person{
public: | public:
unsigned long id; | double salary;
}; | };
class GA : public Student, public Faculty {
public:
std::vector<std::string> labs;
};
Let’s look at the class diagram for this situation…
We started with a simple class hierarchy:
Person, Student, Faculty
Then we changed it so that a GA
inherited from both Student
and Faculty
(which makes sense on some level).
Person, Student, Faculty
Notice the “diamond” shape this created, with Person
as a common ancestor of both parent classes.
Notice the “diamond” shape this created, with Person
as a common ancestor of both parent classes.
Person, Student, Faculty
This situation creates several challenges, and is known in programming circles as the Diamond of Death.
Back to the example…
class Person{
public:
std::string name;
};
class Student : public Person{ | class Faculty : public Person{
public: | public:
unsigned long id; | double salary;
}; | };
class GA : public Student, public Faculty {
public:
std::vector<std::string> labs;
};
int main() {
GA ga1;
ga1.name = "Alice"; // Error!
}
class Person{
public:
std::string name;
};
class Student : public Person{ | class Faculty : public Person{
public: | public:
unsigned long id; | double salary;
}; | };
class GA : public Student, public Faculty
public:
std::vector<std::string> labs;
};
int main() {
GA ga1;
ga1.Student::name = "Alice"; // this works... but...
}
We can use scope resolution… (but it is tedious, and doesn’t solve the redundancy)
class Person{
public:
std::string name;
};
class Student : public Person{ | class Faculty : public Person{
public: | public:
unsigned long id; | double salary;
}; | };
class GA : public Student, public Faculty {
public:
std::vector<std::string> labs;
};
int main() {
GA ga1;
ga1.Student::name = "Alice"; // this works... but...
ga1.Faculty::name = "Bob"; // what about this?
std::cout << "name is: "
<< ga1.Student::name
<< " - or is it - "
<< ga1.Faculty::name << "?\n";
}
class Person{
public:
std::string name;
};
class Student : public Person{ | class Faculty : public Person{
public: | public:
unsigned long id; | double salary;
}; | };
class GA : public Student, public Faculty {
public:
std::vector<std::string> labs;
};
int main() {
GA ga1;
ga1.Student::name = "Alice"; // this works... but...
ga1.Faculty::name = "Bob"; // what about this?
std::cout << "name is: "
<< ga1.Student::name // will be "Alice"
<< " - or is it - "
<< ga1.Faculty::name << "?\n"; // will be "Bob"
}
Ouch.
class Person{
public:
std::string name;
};
class Student | class Faculty
: virtual public Person{ | : virtual public Person{
public: | public:
unsigned long id; | double salary;
}; | };
class GA : public Student, public Faculty {
public:
std::vector<std::string> labs;
};
int main() {
GA ga1;
ga1.name = "Alice"; // now, we've really fixed it!
std::cout << "Name is: " << ga1.name << ".\n";
}
Without virtual
Inheritance:
With virtual
Inheritance
Virtual Methods and Polymorphism
CS 50x2 Accelerated Programming Series