Protected Members and Multiple
Inheritance
|
Learning
Objectives After completing this module,
students should be able to: •
Explain the purpose of protected members and the rule for
accessing them in a derived class. •
Write a class with several base classes and use the members of
all of them. •
Recognise and resolve ambiguity between members inherited from
different bases. •
Explain how a multiply derived object is laid out and how pointer
conversions behave. •
Merge overloads from several bases with using-declarations. |
1. Inheritance and Protected
Members
Module 1 showed that a derived class cannot use the private members of its base class, while public members are open to everybody. Sometimes a base
class wants to share some members only with its derived classes. The
access specifier protected is made
for this purpose.
|
Access
specifier |
Same
class |
Derived
class |
Outside
code |
|
public |
Yes |
Yes |
Yes |
|
protected |
Yes |
Yes |
No |
|
private |
Yes |
No |
No |
Program 2.1: A derived class uses a
protected member of its base class.
#include <iostream>
using namespace std;
class Person {
protected:
int age;
public:
void
setAge(int a) { age = a; }
};
class Student : public Person {
public:
void
displayAge() const
{
cout
<< "Student age: " << age << "\n";
}
/*
protected access works through objects of the derived class only */
void
compareWith(const Student &other) const
{
cout
<< "Other student age: " << other.age <<
"\n";
}
};
int main()
{
Student s,
t;
s.setAge(20);
t.setAge(22);
s.displayAge();
s.compareWith(t);
// cout
<< s.age;
// error: age is protected
return 0;
}
Output:
Student age: 20
Other student age: 22
•
age is
protected in Person. Student
can use it directly in displayAge()
because it is a derived class.
•
main()
cannot use s.age: outside the class
hierarchy a protected member is as hidden as a private one.
•
The public function setAge()
of the base class provides controlled access from outside.
1.1 The rule for protected access
A derived class may access a protected member of the base class only
through objects of its own type (or of a class derived from it). It may not
use it through a plain base-class object or reference, because that object
could belong to a completely different branch of the hierarchy.
In Program 2.1 the function compareWith(const Student &other) uses other.age; this is allowed because other is a Student.
If the parameter were a const Person
&, the same expression would be rejected:
error: ‘int Person::age’ is protected within this
context
1.2 Design advice
•
Protected data weakens encapsulation: any future
derived class can change it, so a change in the base class may break all of
them.
•
Prefer private data with protected member
functions (or public ones) for access. Use protected data only when the
hierarchy is small and tightly controlled.
•
Protected members are also the way to give derived
classes access to helper functions that the outside code must not call.
2. Inheriting Multiple Base
Classes
|
Definition: Multiple
inheritance means that a class has two or more
direct base classes and inherits the members of all of them. |
class DerivedClass : access BaseClass1, access
BaseClass2, ...
{
// members
};
Each base class has its own access specifier. If it is omitted, class assumes private
for that base, so every base normally gets an explicit public.

Figure 2.1: Student
inherits from both Academic and Sports.
Program 2.2: A Student that inherits
academic and sports information.
#include <iostream>
using namespace std;
class Academic {
protected:
int marks;
public:
void
setMarks(int m) { marks = m; }
void
displayMarks() const { cout << "Academic marks: " <<
marks << "\n"; }
};
class Sports {
protected:
int score;
public:
void
setScore(int s) { score = s; }
void
displayScore() const { cout << "Sports score: " << score
<< "\n"; }
};
class Student : public Academic, public Sports {
public:
void
displayResult() const
{
displayMarks();
displayScore();
cout
<< "Total score: " << marks + score <<
"\n";
}
};
int main()
{
Student s;
s.setMarks(85);
s.setScore(10);
s.displayResult();
return 0;
}
Output:
Academic marks: 85
Sports score: 10
Total score: 95
1.
Student
derives publicly from Academic
and Sports. It can call setMarks(), setScore(),
displayMarks() and displayScore().
2.
The protected members marks
and score belong to different bases,
but displayResult() can use both: 85
+ 10 = 95.
3.
The two bases have no members with the same name, so
there is no ambiguity.
2.1 Memory layout and pointer conversions
The object of a multiply derived class contains one subobject for each
base class, followed by the members of the derived class. A pointer to a base
class must point to the start of that base’s subobject, so when a
derived pointer is converted to the pointer of the second base, the compiler adjusts
the address.
Figure 2.2: Layout
of an object of class C : public A, public B. B* points into the middle of the
object.
Program 2.3: Converting a derived pointer
to each of its bases.
#include <iostream>
using namespace std;
class A { public: int a = 1; virtual ~A() {} };
class B { public: int b = 2; virtual ~B() {} };
class C : public A, public B { public: int c = 3; };
int main()
{
C obj;
A *pa =
&obj; //
implicit derived-to-base conversion
B *pb =
&obj; // the
compiler adjusts the address
cout
<< boolalpha;
cout
<< "A subobject at start of C: "
<< (static_cast<void *>(pa) == static_cast<void
*>(&obj)) << endl;
cout
<< "B subobject at start of C: "
<< (static_cast<void *>(pb) == static_cast<void
*>(&obj)) << endl;
cout
<< "pa->a = " << pa->a << ", pb->b =
" << pb->b << endl;
cout
<< "sizeof(C) >= sizeof(A) + sizeof(B): "
<< (sizeof(C) >= sizeof(A) + sizeof(B)) << endl;
return 0;
}
Output (the first two lines depend on the
compiler’s object layout; this is the typical result for GCC and Clang):
A subobject at start of C: true
B subobject at start of C: false
pa->a = 1, pb->b = 2
sizeof(C) >= sizeof(A) + sizeof(B): true
The first base subobject normally starts at the beginning of the object,
while the second does not. A pointer conversion is therefore not just a
copy of the address; this is also why one must never convert with a C-style
cast or reinterpret_cast instead of the
proper conversion (Module 7).
3. Ambiguity in Multiple
Inheritance
If two base classes contain a member with the same name, the
derived class inherits both. A reference to that name through a derived object
is then ambiguous: the compiler cannot decide which one is meant.
Program 2.4: Both bases have display() and value;
the scope resolution operator selects one.
#include <iostream>
using namespace std;
class A {
public:
int value =
10;
void
display() const { cout << "Display function of class A\n"; }
};
class B {
public:
int value =
20;
void
display() const { cout << "Display function of class B\n"; }
};
class C : public A, public B {
};
int main()
{
C obj;
//
obj.display(); // error:
request for member 'display' is ambiguous
// cout
<< obj.value; // error:
request for member 'value' is ambiguous
obj.A::display();
obj.B::display();
cout
<< "Value from A: " << obj.A::value <<
"\n";
cout
<< "Value from B: " << obj.B::value <<
"\n";
return 0;
}
Output:
Display function of class A
Display function of class B
Value from A: 10
Value from B: 20
Writing obj.display(); or obj.value gives the errors:
error: request for member ‘display’ is ambiguous
error: request for member ‘value’ is ambiguous
|
Important: Ambiguity is
detected during name lookup, before access control and before overload
resolution. Even if the two functions differ in the type of their parameters,
a call through the derived object is ambiguous unless the names are made
visible together (Section 3.3). |
3.1 Resolving ambiguity with the scope resolution
operator
Name the class explicitly: obj.A::display()
and obj.B::display(). This works, but
it must be repeated at every call and exposes the structure of the hierarchy.
3.2 Resolving ambiguity by redefining the function
in the derived class
A function declared in the derived class hides both base versions. It can
combine them, giving users one clear interface.
Program 2.5: The derived class defines its
own display().
#include <iostream>
using namespace std;
class A {
public:
void
display() const { cout << "Class A display\n"; }
};
class B {
public:
void
display() const { cout << "Class B display\n"; }
};
class C : public A, public B {
public:
/* one
clear interface for both inherited functions */
void
display() const
{
A::display();
B::display();
}
};
int main()
{
C obj;
obj.display();
return 0;
}
Output:
Class A display
Class B display
3.3 Merging overloads with using-declarations
If the inherited functions have different parameter lists, a
using-declaration brings both into the scope of the derived class, where they
act as ordinary overloads and are chosen by their arguments.
Program 2.6: print(int)
from one base and print(double) from
another.
#include <iostream>
using namespace std;
class Printer {
public:
void
print(int x) const { cout << "Printer::print(int) " << x
<< "\n"; }
};
class Scanner {
public:
void
print(double d) const { cout << "Scanner::print(double) "
<< d << "\n"; }
};
class Machine : public Printer, public Scanner {
public:
/* bring
both functions into Machine: they now form one overload set */
using
Printer::print;
using
Scanner::print;
};
int main()
{
Machine m;
m.print(5); //
chooses print(int)
m.print(2.5); //
chooses print(double)
return 0;
}
Output:
Printer::print(int) 5
Scanner::print(double) 2.5
A using-declaration cannot resolve a clash between members with identical
signatures: the calls would remain ambiguous. It also solves the hiding problem
of Module 1 for a single base class: using
Base::show; makes all show
overloads of Base visible again.
|
Version Note: The
program files for this module use default member initializers (int
value = 10;), which require C++11. In C++98 initialise members
in constructors. |
4. Advantages, Problems and
Guidelines
|
Advantages |
Problems |
|
A class can combine abilities from independent classes |
Name ambiguity between bases |
|
Models objects that belong to several categories (a Student
who is also an Employee) |
The diamond problem: two copies of a common base
(Module 4) |
|
Allows interface classes to be combined (Module 6) |
More complex constructor order and pointer conversions |
|
Reuses several tested classes |
Hierarchies become harder to understand and maintain |
•
Use multiple inheritance mainly to combine one real
base class with one or more interfaces (classes containing only pure
virtual functions).
•
If a common base is shared by the bases, use virtual
inheritance (Module 4).
•
Give functions with the same meaning in different bases
different names where you control the design, to avoid ambiguity.
5. Quick Revision
•
protected
members are visible in the class and its derived classes but not outside; a
derived class reaches them only through objects of its own type.
•
Multiple inheritance: class
C : public A, public B; the object contains an A part and a B part, and
pointer conversion to B adjusts the address.
•
Same member name in two bases ⇒ ambiguous; resolve by A:: / B::, by
redefining in the derived class, or (for different signatures) by
using-declarations.
•
Ambiguity is detected before access control.
6. Important Examination
Questions
Short-answer questions
1.
What is a protected member? How does it differ from
private and public members?
2.
What is multiple inheritance? Give the syntax.
3.
When does ambiguity occur in multiple inheritance? How
is it resolved?
4.
Why must the compiler adjust the address when
converting to the second base class?
5.
State two problems of multiple inheritance.
Long-answer and
programming questions
1.
Explain protected members and write a program in which
a derived class uses a protected member of the base class.
2.
Write a program for a class that inherits from two
classes (Academic and Sports) and displays a combined result.
3.
Write a program in which two base classes have a
function with the same name, show the ambiguity error, and give three ways to
resolve it.
4.
Explain with a diagram how an object of a class with
two base classes is stored in memory.
End of Module 2
Next: Module 3 – Constructors,
Destructors and Inheritance
No comments:
Post a Comment