Programming Pandit

c/c++/c#/Javav/Python


Latest Update

Friday, October 23, 2026

Protected Members and Multiple Inheritance

 

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.

Title: Student inherits from both Academic and Sports. - Description: Student inherits from both Academic and Sports.

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.

Title: Layout of an object of class C : public A, public B. B* points into the middle of the object. - Description: Layout of an object of class C : public A, public B. B* points into the middle of the object.

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