Programming Pandit

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


Latest Update

Friday, October 23, 2026

Granting Access, Friends and Virtual Base Classes


Granting Access, Friends and Virtual Base Classes


Learning Objectives

After completing this module, students should be able to:

•      Grant access to inherited members in private and protected inheritance with using-declarations.

•      Recognise the legacy access-declaration syntax and its status.

•      Use friend functions, friend classes and friend member functions, and state the rules of friendship.

•      Explain the diamond problem and solve it with virtual base classes.

•      Describe constructor and destructor order and the memory cost of virtual inheritance.

 

Important: The topic “Granting Access” of the syllabus means restoring access to members that were made less accessible by protected or private inheritance (Section 1). Friend functions and classes (Section 2) are a related but different way of giving access to selected outside code, and they are included in this module because they are often taught together.

 

1. Granting Access to Inherited Members

1.1 The problem

In private (or protected) inheritance, the public members of the base class become private (or protected) in the derived class (Module 1). Outside code can then no longer use them through a derived object, even when the derived class would like some of them to stay public.

Granting access means making selected inherited members accessible again, with the same access as the section of the derived class in which the declaration is written.

1.2 The using-declaration

class Derived : private Base

{

public:

    using Base::member;      // member is public again in Derived

};

 

Program 4.1: Restoring and raising access.

#include <iostream>

using namespace std;

 

class Base {

public:

    int pub = 1;

    void show() const { cout << "Base::show()\n"; }

 

protected:

    int prot = 2;

 

private:

    int priv = 3;

 

public:

    int getPriv() const { return priv; }

};

 

/* Private inheritance: every public member of Base becomes private in Derived.

   using-declarations in the public section grant access again. */

class Derived : private Base {

public:

    using Base::pub;                     // public again

    using Base::show;                    // public again

    using Base::prot;                    // a protected member becomes public

    // using Base::priv;                 // error: priv is inaccessible in Derived

};

 

int main()

{

    Derived d;

    d.pub = 10;

    d.show();

    cout << "pub  = " << d.pub << "\n";

    cout << "prot = " << d.prot << " (raised from protected to public)\n";

    // d.getPriv();                      // error: not restored, still private

    return 0;

}

 

Output:

Base::show()

pub  = 10

prot = 2 (raised from protected to public)

 

Title: Private inheritance hides all members; using-declarations in a public section grant access again. - Description: Private inheritance hides all members; using-declarations in a public section grant access again.

Figure 4.1: Private inheritance hides all members; using-declarations in a public section grant access again.

1.      Because Derived inherits privately, pub and show() would be private. using Base::pub; and using Base::show; in the public section make them public again.

2.      using Base::prot; makes a protected member of the base class public in Derived: a using-declaration may raise access, as long as the member is accessible to Derived in the first place.

3.      The private member priv can never be granted: it is not accessible in Derived.

 

Trying to grant a private member, or calling a function that has not been restored, gives errors:

error: ‘int Base::priv’ is private within this context

 

error: ‘int Base::getPriv() const’ is inaccessible within this context

 

Rule

Explanation

Access = the section of the declaration

The member gets the access of the section (public, protected or private) where the using-declaration appears

Only accessible members

A base member that is private in the base class cannot be granted

Names, not signatures

using Base::f; names all overloads of f together

Can lower access too

A using-declaration in the private section of a publicly derived class makes the member private in the derived class

Scope

Applies to the derived class and to classes derived from it

 

1.3 The legacy access declaration

Older C++ code uses a shorter syntax without the using keyword, called an access declaration: just the qualified name of the member.

Program 4.2: An access declaration.

// Legacy syntax: an access declaration (no 'using' keyword).

#include <iostream>

using namespace std;

 

class Base {

public:

    int value = 5;

};

 

class Derived : private Base {

public:

    Base::value;                         // access declaration (deprecated)

};

 

int main()

{

    Derived d;

    cout << d.value << "\n";

    return 0;

}

 

Version Note: Access declarations (Base::value;) are deprecated in favour of using-declarations (using Base::value;). They have been deprecated in the C++ standard for a long time, and compilers still accept them with a warning. GCC 13 gives the same warning in every mode from -std=c++98 to -std=c++20:

 

warning: access declarations are deprecated in favour of using-declarations; suggestion: add the ‘using’ keyword [-Wdeprecated]

 

Older books (including the book on which this syllabus is based) describe only the access declaration. Know the old form for examinations, but write using in new code.

2. Friend Functions and Friend Classes

A friend is a function or class that is allowed to access the private and protected members of another class, although it is not a member of it. The class itself grants friendship with the keyword friend.

2.1 Friend function

Program 4.3: A non-member function reads a private member.

#include <iostream>

using namespace std;

 

class Student {

private:

    int marks;

 

public:

    Student(int m) : marks(m) {}

    friend void displayMarks(const Student &s);   // not a member function

};

 

void displayMarks(const Student &s)

{

    cout << "Marks: " << s.marks << "\n";         // private member is accessible

}

 

int main()

{

    Student s(85);

    displayMarks(s);

    return 0;

}

 

Output:

Marks: 85

 

displayMarks() is declared inside Student with friend but defined outside; it is not called with the dot operator and has no this pointer, so the object is passed as an argument.

2.2 Friend class

If a class is declared a friend, all member functions of that class can access the private members of the class that granted friendship.

Program 4.4: An Auditor class that may inspect an Account.

#include <iostream>

using namespace std;

 

class Account {

private:

    double balance;

 

public:

    Account(double b) : balance(b) {}

    friend class Auditor;

};

 

class Auditor {

public:

    void inspect(const Account &a) const

    {

        cout << "Account balance: " << a.balance << "\n";

    }

};

 

int main()

{

    Account account(25000.50);

    Auditor auditor;

    auditor.inspect(account);

    return 0;

}

 

Output:

Account balance: 25000.5

 

Title: Account grants friendship to Auditor. The friendship goes in one direction only. - Description: Account grants friendship to Auditor. The friendship goes in one direction only.

Figure 4.2: Account grants friendship to Auditor. The friendship goes in one direction only.

2.3 Friend member function

Friendship can be given to one member function of another class instead of the whole class. The other class must be declared first, and the friend function must be defined after the class that grants the friendship is complete.

Program 4.5: Only Printer::show() is a friend of Box.

#include <iostream>

using namespace std;

 

class Box;                               // forward declaration

 

class Printer {

public:

    void show(const Box &b) const;       // defined after Box is complete

};

 

class Box {

private:

    int side;

 

public:

    Box(int s) : side(s) {}

    // only this function is a friend

    friend void Printer::show(const Box &b) const;

};

 

void Printer::show(const Box &b) const

{

    cout << "Side of the box: " << b.side << "\n";

}

 

int main()

{

    Box b(7);

    Printer p;

    p.show(b);

    return 0;

}

 

Output:

Side of the box: 7

 

2.4 Friend operator functions

Friend functions are commonly used to overload operators that must have a stream or another type as their left operand, such as operator<< for output.

Program 4.6: An overloaded output operator as a friend.

#include <iostream>

using namespace std;

 

class Point {

private:

    int x, y;

 

public:

    Point(int a, int b) : x(a), y(b) {}

    friend ostream &operator<<(ostream &os, const Point &p);

};

 

ostream &operator<<(ostream &os, const Point &p)

{

    return os << "(" << p.x << ", " << p.y << ")";

}

 

int main()

{

    Point p(3, 4);

    cout << "Point: " << p << "\n";

    return 0;

}

 

Output:

Point: (3, 4)

 

2.5 Rules of friendship

Rule

Meaning

Granted, not taken

A class gives friendship; a function cannot declare itself a friend of another class

Not symmetric

If A makes B a friend, B can access A’s members, but A cannot access B’s

Not transitive

A friend of a friend is not a friend

Not inherited

A friend of the base class is not automatically a friend of its derived classes

Not a member

A friend function has no this pointer and is not affected by the access specifier in which the declaration is written

 

Friendship is not inherited: if F is a friend of Base it may access the private members of Base, but not the private members that Derived adds:

error: ‘int Derived::b’ is private within this context

 

Note: Friends weaken encapsulation. Use them for closely related helpers, operators and testing, not as a general way around access rules.

 

3. Virtual Base Classes

3.1 The diamond problem

Suppose Student and Employee are both derived from Person, and TeachingAssistant is derived from both. Then a TeachingAssistant object reaches Person through two paths (hybrid inheritance, the diamond).

Title: The diamond: TeachingAssistant inherits Person twice. - Description: The diamond: TeachingAssistant inherits Person twice.

Figure 4.3: The diamond: TeachingAssistant inherits Person twice.

With ordinary inheritance the object contains two separate copies of Person: one inside the Student part and one inside the Employee part. This causes two problems: ambiguity when a member of Person is named, and duplicate data.

Program 4.7: Two copies of id.

#include <iostream>

using namespace std;

 

class Person {

public:

    int id = 100;

};

 

class Student : public Person {};

class Employee : public Person {};

 

class TeachingAssistant : public Student, public Employee {};

 

int main()

{

    TeachingAssistant obj;

    // cout << obj.id;                // error: request for member 'id' is ambiguous

    obj.Student::id = 1;              // two separate Person subobjects

    obj.Employee::id = 2;

    cout << "Student path ID : " << obj.Student::id << "\n";

    cout << "Employee path ID: " << obj.Employee::id << "\n";

    cout << "sizeof(TeachingAssistant) = " << sizeof(obj) << "\n";

    return 0;

}

 

Output:

Student path ID : 1

Employee path ID: 2

sizeof(TeachingAssistant) = 8

 

The two id values are independent (1 and 2); the size 8 is two int variables. Naming obj.id directly, or converting to Person *, is an error:

error: request for member ‘id’ is ambiguous

 

error: ‘P’ is an ambiguous base of ‘TA’

 

3.2 The solution: virtual inheritance

If the intermediate classes inherit the common base virtually, all of them share a single copy of it. The keyword virtual is written in the derivation of the intermediate classes:

class Student  : virtual public Person { ... };

class Employee : virtual public Person { ... };

class TeachingAssistant : public Student, public Employee { ... };

 

Program 4.8: A TeachingAssistant with one shared Person.

#include <iostream>

#include <string>

using namespace std;

 

class Person {

public:

    string name;

    Person() : name("Unknown") { cout << "Person constructor\n"; }

};

 

class Student : virtual public Person {

public:

    int marks = 85;

};

 

class Employee : virtual public Person {

public:

    int employeeId = 501;

};

 

class TeachingAssistant : public Student, public Employee {

public:

    TeachingAssistant()

    {

        name = "Rahul";                  // one shared Person: no ambiguity

        cout << "TeachingAssistant constructor\n";

    }

 

    void display() const

    {

        cout << "Name: " << name << "\n";

        cout << "Marks: " << marks << "\n";

        cout << "Employee ID: " << employeeId << "\n";

    }

};

 

int main()

{

    TeachingAssistant obj;

    obj.display();

    return 0;

}

 

Output:

Person constructor

TeachingAssistant constructor

Name: Rahul

Marks: 85

Employee ID: 501

 

•      “Person constructor” appears once: only one Person subobject exists.

•      name can be used without qualification in TeachingAssistant because there is only one name and no ambiguity.

•      The virtual keyword is applied where Student and Employee are declared, before the diamond is formed. It affects the derived classes of every later combination.

 

3.3 Constructors of virtual base classes

Since the shared base may be reached through several paths, the most derived class is responsible for initializing it. Initializer calls to the virtual base in the intermediate classes are ignored when they are part of a larger object.

Program 4.9: Person(100) is called by TeachingAssistant; Person(10) and Person(20) are used only when a Student or Employee is created on its own.

#include <iostream>

using namespace std;

 

class Person {

public:

    int id;

    Person(int n) : id(n) { cout << "Person: " << id << "\n"; }

};

 

class Student : virtual public Person {

public:

    Student() : Person(10) { cout << "Student constructor\n"; }

};

 

class Employee : virtual public Person {

public:

    Employee() : Person(20) { cout << "Employee constructor\n"; }

};

 

class TeachingAssistant : public Student, public Employee {

public:

    TeachingAssistant() : Person(100)    // the most-derived class initializes it

    {

        cout << "TeachingAssistant constructor\n";

    }

};

 

int main()

{

    TeachingAssistant obj;

    cout << "Final ID: " << obj.id << "\n";

    cout << "--- creating a Student directly\n";

    Student s;

    cout << "Student's Person ID: " << s.id << "\n";

    return 0;

}

 

Output:

Person: 100

Student constructor

Employee constructor

TeachingAssistant constructor

Final ID: 100

--- creating a Student directly

Person: 10

Student constructor

Student's Person ID: 10

 

When TeachingAssistant is constructed, its own initializer Person(100) is used once and the calls Person(10) and Person(20) in Student and Employee are skipped. A stand-alone Student uses Person(10). If the virtual base has no default constructor, every class derived from it (directly or indirectly) must be able to initialize it.

3.4 Order of construction and destruction

Virtual base classes are constructed first, before the non-virtual bases, and are destroyed last.

Program 4.10: Top is a virtual base of Left and Right; Bottom derives from both.

#include <iostream>

using namespace std;

 

class Top {

public:

    Top() { cout << "Top constructor\n"; }

    ~Top() { cout << "Top destructor\n"; }

};

 

class Left : virtual public Top {

public:

    Left() { cout << "Left constructor\n"; }

    ~Left() { cout << "Left destructor\n"; }

};

 

class Right : virtual public Top {

public:

    Right() { cout << "Right constructor\n"; }

    ~Right() { cout << "Right destructor\n"; }

};

 

class Bottom : public Left, public Right {

public:

    Bottom() { cout << "Bottom constructor\n"; }

    ~Bottom() { cout << "Bottom destructor\n"; }

};

 

int main()

{

    Bottom b;

    return 0;

}

 

Output:

Top constructor

Left constructor

Right constructor

Bottom constructor

Bottom destructor

Right destructor

Left destructor

Top destructor

 

Top is constructed once and before Left and Right (in declaration order); destruction is the exact reverse.

3.5 The cost of virtual inheritance

To find the shared subobject at run time, a virtual-inheritance class stores hidden information (typically a pointer to a table of offsets, vptr). Accessing the virtual base therefore needs one more indirection, and objects become larger.

Title: Typical object layouts of the diamond: ordinary and virtual inheritance. - Description: Typical object layouts of the diamond: ordinary and virtual inheritance.

Figure 4.4: Typical object layouts of the diamond: ordinary and virtual inheritance.

Program 4.11: Comparing the object sizes.

#include <iostream>

using namespace std;

 

/* Ordinary diamond */

struct P1 { int id; };

struct S1 : P1 {};

struct E1 : P1 {};

struct TA1 : S1, E1 {};

 

/* Virtual diamond */

struct P2 { int id; };

struct S2 : virtual P2 {};

struct E2 : virtual P2 {};

struct TA2 : S2, E2 {};

 

int main()

{

    cout << "Ordinary inheritance: sizeof(TA1) = " << sizeof(TA1)

         << "  (two copies of P1::id)\n";

    cout << "Virtual inheritance : sizeof(TA2) = " << sizeof(TA2)

         << "  (one copy of P2::id plus bookkeeping pointers)\n";

    return 0;

}

 

Output (the sizes are compiler-specific; this is GCC on a 64-bit system):

Ordinary inheritance: sizeof(TA1) = 8  (two copies of P1::id)

Virtual inheritance : sizeof(TA2) = 24  (one copy of P2::id plus bookkeeping pointers)

 

Feature

Ordinary inheritance

Virtual inheritance

Copies of the common base in the final object

One for each path (two in a diamond)

Exactly one

Ambiguity on common-base members

Yes

No

Who initializes the common base

Each intermediate class

The most derived class

Object size and access speed

Smaller; direct access

Larger; extra indirection

Conversion derived → virtual base

Fixed offset

Computed at run time

 

Note: A static_cast from a virtual base class down to a derived class is not allowed; use dynamic_cast (Module 7).

 

Use virtual inheritance only where a real diamond exists. The C++ Standard Library itself uses it in the stream classes (iostream derives from istream and ostream, which share a virtual ios base).

4. Quick Revision

•      Granting access: using Base::member; in the desired section of the derived class restores (or raises, from protected) access; private base members cannot be granted. The old form Base::member; is deprecated.

•      A friend function/class may access private members; friendship is granted, not symmetric, not transitive and not inherited.

•      Diamond inheritance gives two copies of the common base; virtual inheritance gives one.

•      The most derived class initializes a virtual base; virtual bases are constructed first and destroyed last.

 

5. Important Examination Questions

Short-answer questions

1.      What is meant by granting access to inherited members? How is it done?

2.      What is an access declaration? Why is it deprecated?

3.      What is a friend function? Write its syntax.

4.      State three rules of friendship.

5.      What is the diamond problem?

6.      What is a virtual base class? Why is it used?

7.      Who initializes a virtual base class?

 

Long-answer and programming questions

1.      Explain how access to inherited members can be restored in private inheritance, with a program.

2.      Explain friend functions, friend classes and friend member functions with programs.

3.      Explain the diamond problem and solve it with virtual base classes using a program.

4.      Show the order of constructor and destructor calls in a program with a virtual base class.

5.      Compare ordinary and virtual inheritance with respect to object size, ambiguity and initialization.

 

End of Module 4

Next: Module 5 – Polymorphism and Virtual Functions

No comments:

Post a Comment