Show Lecture.MultipleInheritance as a slide show.
CS253 Multiple Inheritance
Multiple Aspects
- A dog is man’s best friend. It is also:
- a guardian
- a walking companion
- a snuggler
- a yappy enemy of mail carriers
- Similarly, I (Logan) am a teacher, but I’m also
a computer programmer, a musician, an adopted child, a husband,
a great uncle, a vegetarian, an atheist, a comic book fan, a Trekkie,
a brony, a native English speaker, a homeowner, a Detroit native,
a Colorado resident, a U.S. citizen, etc.
- Nobody says that you have to be only one thing.
Multiple Inheritance
- In C++, unlike caffiene-based languages, multiple inheritance
is allowed.
- True, a Java class can inherit from several interfaces.
Space Considerations
- I’m running out of room, so I’m going to use struct instead
of class in some declarations.
- In a struct, things are public
by default, so I can omit the
public:
lines,
and also the public in inheritance.
Example
struct Manager {
void nag() { cout << "Grade those labs!\n"; }
};
struct Teacher {
void lecture() { cout << "Avoid endl!\n"; }
};
struct Instructor : public Manager, public Teacher {
void relax() { cout << "Read a comic book.\n"; }
};
Instructor me;
me.nag(); // method inherited from Manager
me.lecture(); // method inherited from Teacher
me.relax(); // method of Instructor
Grade those labs!
Avoid endl!
Read a comic book.
Manager
and Teacher
are independent—no is-a relationship either way.
Example
struct Manager {
void nag() { cout << "Grade those labs!\n"; }
};
struct Teacher {
void lecture() { cout << "Avoid endl!\n"; }
};
struct Instructor : public Manager, public Teacher {
void relax() { cout << "Read a comic book.\n"; }
};
Instructor me;
Manager *m = &me;
m->nag();
Grade those labs!
Example
struct Manager {
void nag() { cout << "Grade those labs!\n"; }
};
struct Teacher {
void lecture() { cout << "Avoid endl!\n"; }
};
struct Instructor : public Manager, public Teacher {
void relax() { cout << "Read a comic book.\n"; }
};
Instructor me;
Manager *m = &me;
m->nag(); // ok
m->lecture(); // 🦡 Not a method of Manager
m->relax(); // 🦡 Not a method of Manager
c.cc:16: error: ‘struct main()::Manager’ has no member named ‘lecture’
c.cc:17: error: ‘struct main()::Manager’ has no member named ‘relax’
No, virtual doesn’t help
struct Manager {
virtual void nag() { cout << "Grade those labs!\n"; }
};
struct Teacher {
virtual void lecture() { cout << "Avoid endl!\n"; }
};
struct Instructor : public Manager, public Teacher {
virtual void relax() { cout << "Read a comic book.\n"; }
};
Instructor me;
Manager *m = &me;
m->nag(); // ok
m->lecture(); // 🦡 fails even with virtual
m->relax(); // 🦡 fails even with virtual
c.cc:16: error: ‘struct main()::Manager’ has no member named ‘lecture’
c.cc:17: error: ‘struct main()::Manager’ has no member named ‘relax’
Ambiguity
class Base1 { public: int x=11; };
class Base2 { public: int x=22; };
class Derived : public Base1, public Base2 {
public:
int y = 33;
};
int main() {
Derived d;
cout << d.x << '\n'; // 🦡 Ambiguous.
cout << d.y << '\n'; // Only one y, in Derived.
}
c.cc:10: error: request for member ‘x’ is ambiguous
c.cc:2: note: candidates are: ‘int Base2::x’
c.cc:1: note: ‘int Base1::x’
Resolving Ambiguity
class Base1 { public: int x=11; };
class Base2 { public: int x=22; };
class Derived : public Base1, public Base2 {
public:
int y = 33;
};
int main() {
Derived d;
cout << d.Base2::x << '\n'; // Dreadful, but works.
cout << d.y << '\n'; // Only one y, in Derived.
}
22
33
Multiple Levels
struct Worker {
Worker() { clog << "Worker\n"; }
int ss_num;
};
struct CSU_Employee : Worker {
int employee_id;
};
struct Instructor : CSU_Employee {
int comics;
};
int main() {
Instructor me;
me.comics = 1000003;
me.employee_id = 812345678;
me.ss_num = 362009871;
cout << me.employee_id;
}
Worker
812345678
Same thing, but squished
struct Worker { Worker() { clog << "Worker\n"; } int ss_num; };
struct CSU_Employee : Worker { int employee_id; };
struct Instructor : CSU_Employee { int comics; };
int main() {
Instructor me;
me.comics = 1000003;
me.employee_id = 812345678;
me.ss_num = 362009871;
cout << me.employee_id;
}
Worker
812345678
The only change is whitespace.
Multiple Inheritance
- However, Jack used to have two jobs:
- teaching Computer Science at CSU
- teaching aerobics at the Fort Collins Club
- Let’s change the program to reflect that.
The Dreaded Diamond
struct Worker { Worker() { clog << "Worker\n"; } int ss_num; };
struct CSU_Employee : Worker { int employee_id; };
struct FCC_Employee : Worker { int employee_id; };
struct Instructor : CSU_Employee, FCC_Employee { int comics; };
int main() {
Instructor me;
me.comics = 1000003; // OK
me.employee_id = 3; // 🦡 ambiguous
me.employee_id = 812345678; // 🦡 ambiguous
me.ss_num = 362009871; // 🦡 ambiguous
}
c.cc:9: error: request for member ‘employee_id’ is ambiguous
c.cc:3: note: candidates are: ‘int FCC_Employee::employee_id’
c.cc:2: note: ‘int CSU_Employee::employee_id’
c.cc:10: error: request for member ‘employee_id’ is ambiguous
c.cc:3: note: candidates are: ‘int FCC_Employee::employee_id’
c.cc:2: note: ‘int CSU_Employee::employee_id’
c.cc:11: error: request for member ‘ss_num’ is ambiguous
c.cc:1: note: candidates are: ‘int Worker::ss_num’
c.cc:1: note: ‘int Worker::ss_num’
There are two employee_id
variables,
and two ss_num
variables.
Inheritance hierarchy
Consider the class hierarchy:
struct Worker { Worker() { clog << "Worker\n"; } int ss_num; };
struct CSU_Employee : Worker { int employee_id; };
struct FCC_Employee : Worker { int employee_id; };
struct Instructor : CSU_Employee, FCC_Employee { int comics; };
┌──────────────┐ ┌──────────────┐
│ Worker │ │ Worker │
└──────────────┘ └──────────────┘
: :
: :
┌──────────────┐ ┌──────────────┐
│ CSU_Employee │ │ FCC_Employee │
└──────────────┘ └──────────────┘
⠡ ⠌
⠡ ⠌
⠡ ⠌
⠡ ⠌
┌────────────┐
│ Instructor │
└────────────┘
- Two problems:
- Both
CSU_Employee
and FCC_Employee
have an employee_id
.
- There are two instances of the
Worker
class.
- Let’s fix the first problem, by qualifying the references
to
employee_id
.
Fix #1
struct Worker { Worker() { clog << "Worker\n"; } int ss_num; };
struct CSU_Employee : Worker { int employee_id; };
struct FCC_Employee : Worker { int employee_id; };
struct Instructor : CSU_Employee, FCC_Employee { int comics; };
int main() {
Instructor me;
me.comics = 1000003;
me.FCC_Employee::employee_id = 3;
me.CSU_Employee::employee_id = 812345678;
me.ss_num = 362009871; // 🦡 still ambiguous
}
c.cc:11: error: request for member ‘ss_num’ is ambiguous
c.cc:1: note: candidates are: ‘int Worker::ss_num’
c.cc:1: note: ‘int Worker::ss_num’
Problem #2
┌──────────────┐ ┌──────────────┐
│ Worker │ │ Worker │
└──────────────┘ └──────────────┘
: :
: :
┌──────────────┐ ┌──────────────┐
│ CSU_Employee │ │ FCC_Employee │
└──────────────┘ └──────────────┘
⠡ ⠌
⠡ ⠌
⠡ ⠌
⠡ ⠌
┌────────────┐
│ Instructor │
└────────────┘
- We still have the problem that there are two instances
of the
Worker
class.
- We solve this using virtual inheritance.
- This has nothing to do with virtual methods
except the word virtual.
Using virtual inheritance
struct Worker { Worker() { clog << "Worker\n"; } int ss_num; };
struct CSU_Employee : virtual Worker { int employee_id; };
struct FCC_Employee : virtual Worker { int employee_id; };
struct Instructor : CSU_Employee, FCC_Employee { int comics; };
int main() {
Instructor me;
me.comics = 1000003;
me.FCC_Employee::employee_id = 3;
me.CSU_Employee::employee_id = 812345678;
me.ss_num = 362009871;
}
Worker
New Inheritance Hierarchy
┌──────────────┐
│ Worker │
└──────────────┘
⠌ ⠡
⠌ ⠡
⠌ ⠡
⠌ ⠡
┌──────────────┐ ┌──────────────┐
│ CSU_Employee │ │ FCC_Employee │
└──────────────┘ └──────────────┘
⠡ ⠌
⠡ ⠌
⠡ ⠌
⠡ ⠌
┌────────────┐
│ Instructor │
└────────────┘
- This hierarchy is called The Dreaded Diamond.
- Now, thanks to virtual inheritance,
we have only a single instance of the
Worker
class,
and both CSU_Employee
and FCC_Employee
inherit from it.
Worker
is a virtual base class of both
CSU_Employee
and FCC_Employee
.
- Therefore, when
CSU_Employee
and FCC_Employee
both
appear in an object hierarchy, Worker
only appears once.
Remember class Loud
,
which announces every method:
int main() {
Loud a, b('X');
a = ++b;
}
Loud::Loud()
Loud::Loud() [c='X']
Loud::operator++() [c='Y']
Loud::operator=(const Loud &) [c='Y']
Loud::~Loud() [c='Y']
Loud::~Loud() [c='Y']
Order of construction
class Gamma : public Loud {
public:
Gamma() : beta('B'), Loud('L'), alpha('A') {
clog << __func__ << '\n';
};
~Gamma() { clog << __func__ << '\n'; }
private:
Loud alpha, beta;
};
Gamma g;
Loud::Loud() [c='L']
Loud::Loud() [c='A']
Loud::Loud() [c='B']
Gamma
~Gamma
Loud::~Loud() [c='B']
Loud::~Loud() [c='A']
Loud::~Loud() [c='L']
- Ctor order: base classes, in order of inheritance,
data members in order of declaration, ctor body
- Dtor order: the reverse of that.
g++ -Wall
warns if a member initialization list is not in
declaration order, for this reason. I suppressed the warning, above.
It makes sense
class FullName {
public:
FullName(string f, string l) {
first = f;
last = l;
}
private:
string first, last;
};
- Really should have used member initialization. Oh, well.
- The ctor has to be able to put stuff into the data members.
- Therefore, when do the data members get constructed?
- They are built before the ctor code
{ … }
executes.
- It the data members were built after the ctor code, then
the values from the ctor code would get overwritten.
That wouldn’t work!
- Similarly, the dtor needs to look at the data members,
so the data members get destroyed after the dtor code
{ … }
executes. I need the name of my temporary file
in order to clean it up.
Destructors
class TempFile {
public:
TempFile() {
// fill the path variable
// create temporary file
}
~TempFile() {
remove(path);
}
private:
string path;
};
- The destructor for a class does not destroy the data members.
- The
~TempFile()
destructor cleans up for the TempFile
object in general.
- After
~TempFile()
finishes, then the compiler automatically
calls path.~string()
.
- That call is made by the compiler, not by you.
- While it is possible to call a destructor explicitly, like that,
I have never done so.
- delete is not the same as “call the destructor”.
- Instead, delete means:
⑴ Call the destructor on the object that a pointer points to.
⑵ Free the memory now containing the corpse.
- delete is a thing that you do on a pointer.
- You wouldn’t delete a string member variable, because you
didn’t create it using new.
- By the way, the verb is generally “destroy”.
A destructor destroys an object, it doesn’t “destruct” it.