15.5. Access Control and Inheritance
FundamentalJust as each class controls the initialization of its own members (§ 15.2.2, p. 598), each class also controls whether its members are accessible to a derived class.
protected
Members
As we’ve seen, a class uses protected
for those members that it is willing to share with its derived classes but wants to protect from general access. The protected
specifier can be thought of as a blend of private
and public
:
- Like
private
,protected
members are inaccessible to users of the class. - Like
public
,protected
members are accessible to members and friends of classes derived from this class.
In addition, protected
has another important property:
- A derived class member or friend may access the
protected
members of the base class only through a derived object. The derived class has no special access to theprotected
members of base-class objects.
To understand this last rule, consider the following example:
class Base {
protected:
int prot_mem; // protected member
};
class Sneaky : public Base {
friend void clobber(Sneaky&); // can access Sneaky::prot_mem
friend void clobber(Base&); // can't access Base::prot_mem
int j; // j is private by default
};
// ok: clobber can access the private and protected members in Sneaky objects
void clobber(Sneaky &s) { s.j = s.prot_mem = 0; }
// error: clobber can't access the protected members in Base
void clobber(Base &b) { b.prot_mem = 0; }
If derived classes (and friends) could access protected members in a base-class object, then our second version of clobber
(that takes a Base&)
would be legal. That function is not a friend of Base
, yet it would be allowed to change an object of type Base
; we could circumvent the protection provided by protected
for any class simply by defining a new class along the lines of Sneaky
.
To prevent such usage, members and friends of a derived class can access the protected
members only in base-class objects that are embedded inside a derived type object; they have no special access to ordinary objects of the base type.
public
, private
, and protected
Inheritance
Access to a member that a class inherits is controlled by a combination of the access specifier for that member in the base class, and the access specifier in the derivation list of the derived class. As an example, consider the following hierarchy:
class Base {
public:
void pub_mem(); // public member
protected:
int prot_mem; // protected member
private:
char priv_mem; // private member
};
struct Pub_Derv : public Base {
// ok: derived classes can access protected members
int f() { return prot_mem; }
// error: private members are inaccessible to derived classes
char g() { return priv_mem; }
};
struct Priv_Derv : private Base {
// private derivation doesn't affect access in the derived class
int f1() const { return prot_mem; }
};
The derivation access specifier has no effect on whether members (and friends) of a derived class may access the members of its own direct base class. Access to the members of a base class is controlled by the access specifiers in the base class itself. Both Pub_Derv
and Priv_Derv
may access the protected
member prot_mem
. Neither may access the private
member priv_mem
.
The purpose of the derivation access specifier is to control the access that users of the derived class—including other classes derived from the derived class—have to the members inherited from Base
:
Pub_Derv d1; // members inherited from Base are public
Priv_Derv d2; // members inherited from Base are private
d1.pub_mem(); // ok: pub_mem is public in the derived class
d2.pub_mem(); // error: pub_mem is private in the derived class
Both Pub_Derv
and Priv_Derv
inherit the pub_mem
function. When the inheritance is public
, members retain their access specification. Thus, d1
can call pub_mem
. In Priv_Derv
, the members of Base
are private
; users of that class may not call pub_mem
.
The derivation access specifier used by a derived class also controls access from classes that inherit from that derived class:
struct Derived_from_Public : public Pub_Derv {
// ok: Base::prot_mem remains protected in Pub_Derv
int use_base() { return prot_mem; }
};
struct Derived_from_Private : public Priv_Derv {
// error: Base::prot_mem is private in Priv_Derv
int use_base() { return prot_mem; }
};
Classes derived from Pub_Derv
may access prot_mem
from Base
because that member remains a protected
member in Pub_Derv
. In contrast, classes derived from Priv_Derv
have no such access. To them, all the members that Priv_Derv
inherited from Base
are private
.
Had we defined another class, say, Prot_Derv
, that used protected
inheritance, the public
members of Base
would be protected
members in that class. Users of Prot_Derv
would have no access to pub_mem
, but the members and friends of Prot_Derv
could access that inherited member.
Accessibility of Derived-to-Base Conversion
TrickyWhether the derived-to-base conversion (§ 15.2.2, p. 597) is accessible depends on which code is trying to use the conversion and may depend on the access specifier used in the derived class’ derivation. Assuming D
inherits from B
:
- User code may use the derived-to-base conversion only if
D
inherits publicly fromB
. User code may not use the conversion ifD
inherits fromB
using eitherprotected
orprivate
. - Member functions and friends of
D
can use the conversion toB
regardless of howD
inherits fromB
. The derived-to-base conversion to a direct base class is always accessible to members and friends of a derived class. - Member functions and friends of classes derived from
D
may use the derived-to-base conversion ifD
inherits fromB
using eitherpublic
orprotected
. Such code may not use the conversion ifD
inherits privately fromB
.
TIP
For any given point in your code, if a public
member of the base class would be accessible, then the derived-to-base conversion is also accessible, and not otherwise.
INFO
Key Concept: Class Design and protected
Members
In the absence of inheritance, we can think of a class as having two different kinds of users: ordinary users and implementors. Ordinary users write code that uses objects of the class type; such code can access only the public
(interface) members of the class. Implementors write the code contained in the members and friends of the class. The members and friends of the class can access both the public
and private
(implementation) sections.
Under inheritance, there is a third kind of user, namely, derived classes. A base class makes protected
those parts of its implementation that it is willing to let its derived classes use. The protected
members remain inaccessible to ordinary user code; private
members remain inaccessible to derived classes and their friends.
Like any other class, a class that is used as a base class makes its interface members public
. A class that is used as a base class may divide its implementation into those members that are accessible to derived classes and those that remain accessible only to the base class and its friends. An implementation member should be protected
if it provides an operation or data that a derived class will need to use in its own implementation. Otherwise, implementation members should be private
.
Friendship and Inheritance
Just as friendship is not transitive (§7.3.4, p. 279), friendship is also not inherited. Friends of the base have no special access to members of its derived classes, and friends of a derived class have no special access to the base class:
class Base {
// added friend declaration; other members as before
friend class Pal; // Pal has no access to classes derived from Base
};
class Pal {
public:
int f(Base b) { return b.prot_mem; } // ok: Pal is a friend of Base
int f2(Sneaky s) { return s.j; } // error: Pal not friend of Sneaky
// access to a base class is controlled by the base class, even inside a derived object
int f3(Sneaky s) { return s.prot_mem; } // ok: Pal is a friend
};
The fact that f3
is legal may seem surprising, but it follows directly from the notion that each class controls access to its own members. Pal
is a friend of Base
, so Pal
can access the members of Base
objects. That access includes access to Base
objects that are embedded in an object of a type derived from Base
.
When a class makes another class a friend, it is only that class to which friendship is granted. The base classes of, and classes derived from, the friend have no special access to the befriending class:
// D2 has no access to protected or private members in Base
class D2 : public Pal {
public:
int mem(Base b)
{ return b.prot_mem; } // error: friendship doesn't inherit
};
INFO
Friendship is not inherited; each class controls access to its members.
Exempting Individual Members
Sometimes we need to change the access level of a name that a derived class inherits. We can do so by providing a using
declaration (§3.1, p. 82):
class Base {
public:
std::size_t size() const { return n; }
protected:
std::size_t n;
};
class Derived : private Base { // note: private inheritance
public:
// maintain access levels for members related to the size of the object
using Base::size;
protected:
using Base::n;
};
Because Derived
uses private
inheritance, the inherited members, size
and n
, are (by default) private
members of Derived
. The using
declarations adjust the accessibility of these members. Users of Derived
can access the size
member, and classes subsequently derived from Derived
can access n
.
A using
declaration inside a class can name any accessible (e.g., not private
) member of a direct or indirect base class. Access to a name specified in a using
declaration depends on the access specifier preceding the using
declaration. That is, if a using
declaration appears in a private
part of the class, that name is accessible to members and friends only. If the declaration is in a public
section, the name is available to all users of the class. If the declaration is in a protected
section, the name is accessible to the members, friends, and derived classes.
INFO
A derived class may provide a using
declaration only for names it is permitted to access.
Default Inheritance Protection Levels
In §7.2 (p. 268) we saw that classes defined with the struct
and class
keywords have different default access specifiers. Similarly, the default derivation specifier depends on which keyword is used to define a derived class. By default, a derived class defined with the class
keyword has private
inheritance; a derived class defined with struct
has public
inheritance:
class Base { /* ... */ };
struct D1 : Base { /* ... */ }; // public inheritance by default
class D2 : Base { /* ... */ }; // private inheritance by default
It is a common misconception to think that there are deeper differences between classes defined using the struct
keyword and those defined using class
. The only differences are the default access specifier for members and the default derivation access specifier. There are no other distinctions.
TIP
Best Practices
A privately derived class should specify private
explicitly rather than rely on the default. Being explicit makes it clear that private inheritance is intended and not an oversight.
INFO
Exercises Section 15.5
Exercise 15.18: Given the classes from page 612 and page 613, and assuming each object has the type specified in the comments, determine which of these assignments are legal. Explain why those that are illegal aren’t allowed:
Base *p = &d1; // d1 has type Pub_Derv
p = &d2; // d2 has type Priv_Derv
p = &d3; // d3 has type Prot_Derv
p = &dd1; // dd1 has type Derived_from_Public
p = &dd2; // dd2 has type Derived_from_Private
p = &dd3; // dd3 has type Derived_from_Protected
Exercise 15.19: Assume that each of the classes from page 612 and page 613 has a member function of the form:
void memfcn(Base &b) { b = *this; }
For each class, determine whether this function would be legal.
Exercise 15.20: Write code to test your answers to the previous two exercises.
Exercise 15.21: Choose one of the following general abstractions containing a family of types (or choose one of your own). Organize the types into an inheritance hierarchy:
(a) Graphical file formats (such as gif, tiff, jpeg, bmp)
(b) Geometric primitives (such as box, circle, sphere, cone)
(c) C++ language types (such as class, function, member function)
Exercise 15.22: For the class you chose in the previous exercise, identify some of the likely virtual functions as well as public
and protected
members.