Derived Classes - Abstraction Mechanisms - The C++ Programming Language (2013)

The C++ Programming Language (2013)

Part III: Abstraction Mechanisms

20. Derived Classes

Do not multiply objects without necessity.

– William Occam

Introduction

Derived Classes

Member Functions; Constructors and Destructors

Class Hierarchies

Type Fields; Virtual Functions; Explicit Qualification; Override Control; using Base Members; Return Type Relaxation

Abstract Classes

Access Control

protected Members; Access to Base Classes; using-Declarations and Access Control

Pointers to Members

Pointers to Function Members; Pointers to Data Members; Base and Derived Members

Advice

20.1. Introduction

From Simula, C++ borrowed the ideas of classes and class hierarchies. In addition, it borrowed the design idea that classes should be used to model concepts in the programmer’s and the application’s world. C++ provides language constructs that directly support these design notions. Conversely, using the language features in support of design ideas distinguishes effective use of C++. Using language constructs as just notational props for traditional types of programming is to miss key strengths of C++.

A concept (idea, notion, etc.) does not exist in isolation. It coexists with related concepts and derives much of its power from relationships with other concepts. For example, try to explain what a car is. Soon you’ll have introduced the notions of wheels, engines, drivers, pedestrians, trucks, ambulances, roads, oil, speeding tickets, motels, etc. Since we use classes to represent concepts, the issue becomes how to represent relationships among concepts. However, we can’t express arbitrary relationships directly in a programming language. Even if we could, we wouldn’t want to. To be useful, our classes should be more narrowly defined than our everyday concepts – and more precise.

The notion of a derived class and its associated language mechanisms are provided to express hierarchical relationships, that is, to express commonality between classes. For example, the concepts of a circle and a triangle are related in that they are both shapes; that is, they have the concept of a shape in common. Thus, we explicitly define class Circle and class Triangle to have class Shape in common. In that case, the common class, here Shape, is referred to as the base class or superclass and classes derived from that, here Circle and Triangle, are referred to asderived classes or subclasses. Representing a circle and a triangle in a program without involving the notion of a shape would be to miss something essential. This chapter is an exploration of the implications of this simple idea, which is the basis for what is commonly called object-oriented programming. The language features support building new classes from existing ones:

Implementation inheritance: to save implementation effort by sharing facilities provided by a base class

Interface inheritance: to allow different derived classes to be used interchangeably through the interface provided by a common base class Interface inheritance is often referred to as run-time polymorphism (or dynamic polymorphism).

In contrast, the uniform use of classes not related by inheritance provided by templates (§3.4, Chapter 23) is often referred to as compile-time polymorphism (or static polymorphism).

The discussion of class hierarchies is organized into three chapters:

Derived Classes (Chapter 20): This chapter introduces the basic language features supporting object-oriented programming. Base and derived classes, virtual functions, and access control are covered.

Class Hierarchies (Chapter 21): This chapter focuses on the use of base and derived classes to effectively organize code around the notion of class hierarchies. Most of this chapter is devoted to discussion of programming techniques, but technical aspects of multiple inheritance (classes with more than one base class) are also covered.

Run-time Type Identification (Chapter 22): This chapter describes the techniques for explicitly navigating class hierarchies. In particular, the type conversion operations dynamic_cast and static_cast are presented, as is the operation for determining the type of an object given one of its base classes (typeid).

A brief introduction to the basic idea of hierarchical organization of types can be found in Chapter 3: base and derived classes (§3.2.2) and virtual functions (§3.2.3). These chapters examine these fundamental features and their associated programming and design techniques in greater detail.

20.2. Derived Classes

Consider building a program dealing with people employed by a firm. Such a program might have a data structure like this:

struct Employee {
string first_name, family_name;
char middle_initial;
Date hiring_date;
short department;
//
...
};

Next, we might try to define a manager:

struct Manager {
Employee emp; //
manager's employee record
list<Employee*> group; // people managed
short level;
//
...
};

A manager is also an employee; the Employee data is stored in the emp member of a Manager object. This may be obvious to a human reader – especially a careful reader – but there is nothing that tells the compiler and other tools that Manager is also an Employee. A Manager* is not anEmployee*, so one cannot simply use one where the other is required. In particular, one cannot put a Manager onto a list of Employees without writing special code. We could either use explicit type conversion on a Manager* or put the address of the emp member onto a list of employees. However, both solutions are inelegant and can be quite obscure. The correct approach is to explicitly state that a Manager is an Employee, with a few pieces of information added:

struct Manager : public Employee {
list<Employee*> group;
short level;
//
...
};

The Manager is derived from Employee, and conversely, Employee is a base class for Manager. The class Manager has the members of class Employee (first_name, department, etc.) in addition to its own members (group, level, etc.).

Derivation is often represented graphically by a pointer from the derived class to its base class indicating that the derived class refers to its base (rather than the other way around):

Image

A derived class is often said to inherit properties from its base, so the relationship is also called inheritance. A base class is sometimes called a superclass and a derived class a subclass. This terminology, however, is confusing to people who observe that the data in a derived class object is a superset of the data of an object of its base class. A derived class is typically larger (and never smaller) than its base class in the sense that it holds more data and provides more functions.

A popular and efficient implementation of the notion of derived classes has an object of the derived class represented as an object of the base class, with the information belonging specifically to the derived class added at the end. For example:

Image

No memory overhead is implied by deriving a class. The space required is just the space required by the members.

Deriving Manager from Employee in this way makes Manager a subtype of Employee, so that a Manager can be used wherever an Employee is acceptable. For example, we can now create a list of Employees, some of whom are Managers:

void f(Manager m1, Employee e1)
{
list<Employee*> elist {&m1,&e1);
//
...
}

A Manager is (also) an Employee, so a Manager* can be used as an Employee*. Similarly, a Manager& can be used as an Employee&. However, an Employee is not necessarily a Manager, so an Employee* cannot be used as a Manager*. In general, if a class Derived has a public base class (§20.5) Base, then a Derived* can be assigned to a variable of type Base* without the use of explicit type conversion. The opposite conversion, from Base* to Derived*, must be explicit. For example:

void g(Manager mm, Employee ee)
{
Employee* pe = &mm; //
OK: every Manager is an Employee
Manager* pm = ⅇ // error: not every Employee is a Manager

pm–>level = 2; // disaster: ee doesn't have a level

pm = static_cast<Manager*>(pe); // brute force: works because pe points
// to the Manager mm

pm–>level = 2; // fine: pm points to the Manager mm that has a level
}

In other words, an object of a derived class can be treated as an object of its base class when manipulated through pointers and references. The opposite is not true. The use of static_cast and dynamic_cast is discussed in §22.2.

Using a class as a base is equivalent to defining an (unnamed) object of that class. Consequently, a class must be defined in order to be used as a base (§8.2.2):

class Employee; // declaration only, no definition

class Manager : public Employee { // error: Employee not defined
// ...
};

20.2.1. Member Functions

Simple data structures, such as Employee and Manager, are really not that interesting and often not particularly useful. We need to provide a proper type with a suitable set of operations, and we need to do so without being tied to the details of a particular representation. For example:

class Employee {
public:
void print() const;
string full_name() const { return first_name + ' ' + middle_initial + ' ' + family_name; }
//
...
private:
string first_name, family_name;
char middle_initial;
//
...
};

class Manager : public Employee {
public:
void print() const;
//
...
};

A member of a derived class can use the public – and protected (see §20.5) – members of a base class as if they were declared in the derived class itself. For example:

void Manager::print() const
{
cout << "name is " << full_name() << '\n';
//
...
}

However, a derived class cannot access private members of a base class:

void Manager::print() const
{
cout << " name is " << family_name << '\n'; //
error!
// ...
}

This second version of Manager::print() will not compile because family_name is not accessible to Manager::print().

This comes as a surprise to some, but consider the alternative: that a member function of a derived class could access the private members of its base class. The concept of a private member would be rendered meaningless by allowing a programmer to gain access to the private part of a class simply by deriving a new class from it. Furthermore, one could no longer find all uses of a private name by looking at the functions declared as members and friends of that class. One would have to examine every source file of the complete program for derived classes, then examine every function of those classes, then find every class derived from those classes, etc. This is, at best, tedious and often impractical. Where it is acceptable, protected – rather than private – members can be used (§20.5).

Typically, the cleanest solution is for the derived class to use only the public members of its base class. For example:

void Manager::print() const
{
Employee::print(); //
print Employee information
cout << level; // print Manager-specific information
// ...
}

Note that :: must be used because print() has been redefined in Manager. Such reuse of names is typical. The unwary might write this:

void Manager::print() const
{
print(); //
oops!
// print Manager-specific information
}

The result is a sequence of recursive calls ending with some form of program crash.

20.2.2. Constructors and Destructors

As usual, constructors and destructors are as essential:

• Objects are constructed from the bottom up (base before member and member before derived) and destroyed top-down (derived before member and member before base); §17.2.3.

• Each class can initialize its members and bases (but not directly members or bases of its bases); §17.4.1.

• Typically, destructors in a hierarchy need to be virtual; §17.2.5.

• Copy constructors of classes in a hierarchy should be used with care (if at all) to avoid slicing; §17.5.1.4.

• The resolution of a virtual function call, a dynamic_cast, or a typeid() in a constructor or destructor reflects the stage of construction and destruction (rather than the type of the yet-to-be-completed object); §22.4.

In computer science “up” and “down” can get very confused. In source text, definitions of base classes must occur before the definitions of their derived classes. This implies that for small examples, the bases appear above the derived classes on a screen. Furthermore, we tend to draw trees with the root on top. However, when I talk about constructing objects from the bottom up, I mean starting with the most fundamental (e.g., base classes) and building what depends on that (e.g., derived classes) later. We build from the roots (base classes) toward the leaves (derived classes).

20.3. Class Hierarchies

A derived class can itself be a base class. For example:

class Employee { /* ... */ };
class Manager : public Employee { /*
... */ };
class Director : public Manager { /*
... */ };

Such a set of related classes is traditionally called a class hierarchy. Such a hierarchy is most often a tree, but it can also be a more general graph structure. For example:

class Temporary { /* ... */ };
class Assistant : public Employee { /*
... */ };
class Temp : public Temporary, public Assistant { /*
... */ };
class Consultant : public Temporary, public Manager { /*
... */ };

or graphically:

Image

Thus, as is explained in detail in §21.3, C++ can express a directed acyclic graph of classes.

20.3.1. Type Fields

To use derived classes as more than a convenient shorthand in declarations, we must solve the following problem: Given a pointer of type Base*, to which derived type does the object pointed to really belong? There are four fundamental solutions:

[1] Ensure that only objects of a single type are pointed to (§3.4, Chapter 23).

[2] Place a type field in the base class for the functions to inspect.

[3] Use dynamic_cast22.2, §22.6).

[4] Use virtual functions (§3.2.3, §20.3.2).

Unless you have used final20.3.4.2), solution 1 relies on more knowledge about the types involved than is available to the compiler. In general, it is not a good idea to try to be smarter than the type system, but (especially in combination with the use of templates) it can be used to implement homogeneous containers (e.g., the standard-library vector and map) with unsurpassed performance. Solutions [2], [3], and [4] can be used to build heterogeneous lists, that is, lists of (pointers to) objects of several different types. Solution [3] is a language-supported variant of solution [2]. Solution [4] is a special type-safe variation of solution [2]. Combinations of solutions [1] and [4] are particularly interesting and powerful; in almost all situations, they yield cleaner code than do solutions [2] and [3].

Let us first examine the simple type-field solution to see why it is typically best avoided. The manager/employee example could be redefined like this:

struct Employee {
enum Empl_type { man, empl };
Empl_type type;

Employee() : type{empl} { }

string first_name, family_name;
char middle_initial;

Date hiring_date;
short department;
//
...
};

struct Manager : public Employee {
Manager() { type = man; }

list<Employee*> group; //
people managed
short level;
//
...
};

Given this, we can now write a function that prints information about each Employee:

void print_employee(const Employee* e)
{
switch (e–>type) {
case Employee::empl:
cout << e–>family_name << '\t' << e–>department << '\n';
//
...
break;
case Employee::man:
{ cout << e–>family_name << '\t' << e–>department << '\n';

// ...
const Manager* p = static_cast<const Manager*>(e);
cout << " level " << p–>level << '\n';
//
...
break;
}
}
}

and use it to print a list of Employees, like this:

void print_list(const list<Employee*>& elist)
{
for (auto x : elist)
print_employee(x);
}

This works fine, especially in a small program maintained by a single person. However, it has a fundamental weakness in that it depends on the programmer manipulating types in a way that cannot be checked by the compiler. This problem is usually made worse because functions such asprint_employee() are often organized to take advantage of the commonality of the classes involved:

void print_employee(const Employee* e)
{
cout << e–>family_name << '\t' << e–>department << '\n';
//
...
if (e–>type == Employee::man) {
const Manager* p = static_cast<const Manager*>(e);
cout << " level " << p–>level << '\n';
//
...
}
}

Finding all such tests on the type field buried in a large function that handles many derived classes can be difficult. Even when they have been found, understanding what is going on can be difficult. Furthermore, any addition of a new kind of Employee involves a change to all the key functions in a system – the ones containing the tests on the type field. The programmer must consider every function that could conceivably need a test on the type field after a change. This implies the need to access critical source code and the resulting necessary overhead of testing the affected code. The use of an explicit type conversion is a strong hint that improvement is possible.

In other words, use of a type field is an error-prone technique that leads to maintenance problems. The problems increase in severity as the size of the program increases because the use of a type field causes a violation of the ideals of modularity and data hiding. Each function using a type field must know about the representation and other details of the implementation of every class derived from the one containing the type field.

It also seems that any common data accessible from every derived class, such as a type field, tempts people to add more such data. The common base thus becomes the repository of all kinds of “useful information.” This, in turn, gets the implementation of the base and derived classes intertwined in ways that are most undesirable. In a large class hierarchy, accessible (not private) data in a common base class becomes the “global variables” of the hierarchy. For clean design and simpler maintenance, we want to keep separate issues separate and avoid mutual dependencies.

20.3.2. Virtual Functions

Virtual functions overcome the problems with the type-field solution by allowing the programmer to declare functions in a base class that can be redefined in each derived class. The compiler and linker will guarantee the correct correspondence between objects and the functions applied to them. For example:

class Employee {
public:
Employee(const string& name, int dept);
virtual void print() const;
//
...
private:
string first_name, family_name;
short department;
//
...
};

The keyword virtual indicates that print() can act as an interface to the print() function defined in this class and print() functions defined in classes derived from it. Where such print() functions are defined in derived classes, the compiler ensures that the right print() for the givenEmployee object is invoked in each case.

To allow a virtual function declaration to act as an interface to functions defined in derived classes, the argument types specified for a function in a derived class cannot differ from the argument types declared in the base, and only very slight changes are allowed for the return type (§20.3.6). A virtual member function is sometimes called a method.

A virtual function must be defined for the class in which it is first declared (unless it is declared to be a pure virtual function; see §20.4). For example:

void Employee::print() const
{
cout << family_name << '\t' << department << '\n';
//
...
}

A virtual function can be used even if no class is derived from its class, and a derived class that does not need its own version of a virtual function need not provide one. When deriving a class, simply provide an appropriate function if it is needed. For example:

class Manager : public Employee {
public:

Manager(const string& name, int dept, int lvl);
void print() const;
//
...
private:
list<Employee*> group;
short level;
//
...
};

void Manager::print() const
{
Employee::print();
cout << "\tlevel " << level << '\n';
//
...
}

A function from a derived class with the same name and the same set of argument types as a virtual function in a base is said to override the base class version of the virtual function. Furthermore, it is possible to override a virtual function from a base with a more derived return type (§20.3.6).

Except where we explicitly say which version of a virtual function is called (as in the call Employee::print()), the overriding function is chosen as the most appropriate for the object for which it is called. Independently of which base class (interface) is used to access an object, we always get the same function when we use the virtual function call mechanism.

The global function print_employee()20.3.1) is now unnecessary because the print() member functions have taken its place. A list of Employees can be printed like this:

void print_list(const list<Employee*>& s)
{
for (auto x : s)
x–>print();
}

Each Employee will be written out according to its type. For example:

int main()
{
Employee e {"Brown",1234};
Manager m {"Smith",1234,2};

print_list({&e,&m});
}

produced:

Smith 1234
level 2
Brown 1234

Note that this will work even if print_list() was written and compiled before the specific derived class Manager was even conceived of! This is a key aspect of classes. When used properly, it becomes the cornerstone of object-oriented designs and provides a degree of stability to an evolving program.

Getting “the right” behavior from Employee’s functions independently of exactly what kind of Employee is actually used is called polymorphism. A type with virtual functions is called a polymorphic type or (more precisely) a run-time polymorphic type. To get runtime polymorphic behavior in C++, the member functions called must be virtual and objects must be manipulated through pointers or references. When manipulating an object directly (rather than through a pointer or reference), its exact type is known by the compiler so that run-time polymorphism is not needed.

By default, a function that overrides a virtual function itself becomes virtual. We can, but do not have to, repeat virtual in a derived class. I don’t recommend repeating virtual. If you want to be explicit, use override20.3.4.1).

Clearly, to implement polymorphism, the compiler must store some kind of type information in each object of class Employee and use it to call the right version of the virtual function print(). In a typical implementation, the space taken is just enough to hold a pointer (§3.2.3): the usual implementation technique is for the compiler to convert the name of a virtual function into an index into a table of pointers to functions. That table is usually called the virtual function table or simply the vtbl. Each class with virtual functions has its own vtbl identifying its virtual functions. This can be represented graphically like this:

Image

The functions in the vtbl allow the object to be used correctly even when the size of the object and the layout of its data are unknown to the caller. The implementation of a caller need only know the location of the vtbl in an Employee and the index used for each virtual function. This virtual call mechanism can be made almost as efficient as the “normal function call” mechanism (within 25%), so efficiency concerns should not deter anyone from using a virtual function where an ordinary function call would be acceptably efficient. Its space overhead is one pointer in each object of a class with virtual functions plus one vtbl for each such class. You pay this overhead only for objects of a class with a virtual function. You choose to pay this overhead only if you need the added functionality virtual functions provide. Had you chosen to use the alternative type-field solution, a comparable amount of space would have been needed for the type field.

A virtual function invoked from a constructor or a destructor reflects that the object is partially constructed or partially destroyed (§22.4). It is therefore typically a bad idea to call a virtual function from a constructor or a destructor.

20.3.3. Explicit Qualification

Calling a function using the scope resolution operator, ::, as is done in Manager::print() ensures that the virtual mechanism is not used:

void Manager::print() const
{
Employee::print(); //
not a virtual call
cout << "\tlevel " << level << '\n';
//
...
}

Otherwise, Manager::print() would suffer an infinite recursion. The use of a qualified name has another desirable effect. That is, if a virtual function is also inline (as is not uncommon), then inline substitution can be used for calls specified using ::. This provides the programmer with an efficient way to handle some important special cases in which one virtual function calls another for the same object. The Manager::print() function is an example of this. Because the type of the object is determined in the call of Manager::print(), it need not be dynamically determined again for the resulting call of Employee::print().

20.3.4. Override Control

If you declare a function in a derived class that has exactly the same name and type as a virtual function in a base class, then the function in the derived class overrides the one in the base class. That’s a simple and effective rule. However, for larger class hierarchies it can be difficult to be sure that you actually override the function you meant to override. Consider:

struct B0 {
void f(int) const;
virtual void g(double);
};

struct B1 : B0 { /*
... */ };
struct B2 : B1 { /*
... */ };
struct B3 : B2 { /*
... */ };
struct B4 : B3 { /*
... */ };
struct B5 : B4 { /*
... */ };

struct D : B5 {
void f(int) const; //
override f() in base class
void g(int); // override g() in base class
virtual int h(); // override h() in base class
};

This illustrates three errors that are far from obvious when they appear in a real class hierarchy where the classes B0...B5 have many members each and are scattered over many header files. Here:

B0::f() is not virtual, so you can’t override it, only hide it (§20.3.5).

D::g() doesn’t have the same argument type as B0::g(), so if it overrides anything it’s not the virtual function B0::g(). Most likely, D::g() just hides B0::g().

• There is no function called h() in B0, if D::h() overrides anything, it is not a function from B0. Most likely, it is introducing a brand-new virtual function.

I didn’t show you what was in B1...B5, so maybe something completely different is going on because of declarations in those classes. I personally don’t (redundantly) use virtual for a function that’s meant to override. For smaller programs (especially with a compiler with decent warnings against common mistakes) getting overriding done correctly isn’t hard. However, for larger hierarchies more specific controls are useful:

virtual: The function may be overridden (§20.3.2).

=0: The function must be virtual and must be overridden (§20.4).

override: The function is meant to override a virtual function in a base class (§20.3.4.1).

final: The function is not meant to be overridden (§20.3.4.2).

In the absence of any of these controls, a non-static member function is virtual if and only if it overrides a virtual function in a base class (§20.3.2).

A compiler can warn against inconsistent use of explicit override controls. For example, a class declaration that uses override for seven out of nine virtual base class functions could be confusing to maintainers.

20.3.4.1. override

We can be explicit about our desire to override:

struct D : B5 {
void f(int) const override; //
error: B0::f() is not virtual
void g(int) override; // error: B0::f() takes a double argument
virtual int h() override; // error: no function h() to override
};

Given this definition (and assuming that the intermediate base classes B1...B5 do not provide relevant functions), all three declarations give errors.

In a large or complicated class hierarchy with many virtual functions, it is best to use virtual only to introduce a new virtual function and to use override on all functions intended as overriders. Using override is a bit verbose but clarifies the programmer’s intent.

The override specifier comes last in a declaration, after all other parts. For example:

void f(int) const noexcept override;// OK (if there is a suitable f() to override)
override void f(int) const noexcept;// syntax error
void f(int) override const noexcept;// syntax error

And yes, it’s illogical that virtual is a prefix and override is a suffix. This is part of the price we pay for compatibility and stability over decades.

An override specifier is not part of the type of a function and cannot be repeated in an out-of-class definition. For example:

class Derived : public Base {
void f() override; //
OK if Base has a virtual f()
void g() override; // OK if Base has a virtual g()
};

void Derived::f() override // error: override out of class
{
// ...
}

void g() // OK
{
// ...
}

Curiously, override is not a keyword; it is what is called a contextual keyword. That is, override has a special meaning in a few contexts but can be used as an identifier elsewhere. For example:

int override = 7;

struct Dx : Base {
int override;

int f() override
{
return override + ::override;
}
};

Don’t indulge in such cleverness; it complicates maintenance. The only reason that override is a contextual keyword, rather than an ordinary keyword, is that there exists a significant amount of code that has used override as an ordinary identifier for decades. The other contextual keyword is final20.3.4.2).

20.3.4.2. final

When we declare a member function, we have a choice between virtual and not virtual (the default). We use virtual for functions we want writers of derived classes to be able to define or redefine. We base our choice on the meaning (semantics) of our class:

• Can we imagine the need for further derived classes?

• Does a designer of a derived class need to redefine the function to achieve a plausible aim?

• Is overriding a function error-prone (i.e., is it hard for an overriding function to provide the expected semantics of a virtual function)?

If the answer is “no” to all three questions, we can leave the function non-virtual to gain simplicity of design and occasionally some performance (mostly from inlining). The standard library is full of examples of this.

Far more rarely, we have a class hierarchy that starts out with virtual functions, but after the definition of a set of derived classes, one of the answers becomes “no.” For example, we can imagine an abstract syntax tree for a language where all language constructs have been defined as concrete node classes derived from a few interfaces. We only need to derive a new class if we change the language. In that case, we might want to prevent our users from overriding virtual functions because the only thing such overrides could do would be to change the semantics of our language. That is, we might want to close our design to modification from its users. For example:

struct Node { // interface class
virtual Type type() = 0;
//
...
};

class If_statement : public Node {
public:
Type type() override final; //
prevent further overriding
// ...
};

In a realistic class hierarchy, there would be several intermediate classes between the general interface (here, Node) and the derived class representing a specific language construct (here, If_statement). However, the key point about this example is that Node::type() is meant to be overridden (that’s why it’s declared virtual) and its overrider If_statement::type() is not (that’s why it’s declared final). After using final for a member function, it can no longer be overridden and an attempt to do so is an error. For example:

class Modified_if_statement : public If_statement {
public:
Type type() override; //
error: If_statement::type() is final
// ...
};

We can make every virtual member function of a class final; just add final after the class name. For example:

class For_statement final : public Node {
public:
Type type() override;
//
...
};

class Modified_for_statement : public For_statement { // error: For_statement is final
Type type() override;
//
...
};

For good and bad, adding final to the class not only prevents overriding, it also prevents further derivation from a class. There are people who use final to try to gain performance – after all, a non-virtual function is faster than a virtual one (by maybe 25% on a modern implementation) and offers greater opportunities for inlining (§12.1.5). However, do not blindly use final as an optimization aid; it affects the class hierarchy design (often negatively), and the performance improvements are rarely significant. Do some serious measurements before claiming efficiency improvements. Use final where it clearly reflects a class hierarchy design that you consider proper. That is, use final to reflect a semantic need.

A final specifier is not part of the type of a function and cannot be repeated in an out-of-class definition. For example:

class Derived : public Base {
void f() final; //
OK if Base has a virtual f()
void g() final; // OK if Base has a virtual g()
// ...
};

void Derived::f() final // error: final out of class
{
// ...
}
void g() final // OK
{
// ...
}

Like override20.3.4.1), final is a contextual keyword. That is, final has a special meaning in a few contexts but can be used as an ordinary identifier elsewhere. For example:

int final = 7;

struct Dx : Base {
int final;


int f() final
{
return final + ::final;
}

};

Don’t indulge in such cleverness; it complicates maintenance. The only reason that final is a contextual keyword, rather than an ordinary keyword, is that there exist a significant amount of code that has used final as an ordinary identifier for decades. The other contextual keyword isoverride20.3.4.1).

20.3.5. using Base Members

Functions do not overload across scopes (§12.3.3). For example:

struct Base {
void f(int);
};

struct Derived : Base {
void f(double);
};

void use(Derived d)
{
d.f(1); //
call Derived::f(double)
Base& br = d
br.f(1); //
call Base::f(int)
}

This can surprise people, and sometimes we want overloading to ensure that the best matching member function is used. As for namespaces, using-declarations can be used to add a function to a scope. For example:

struct D2 : Base {
using Base::f; //
bring all fs from Base into D2
void f(double);
};

void use2(D2 d)
{
d.f(1); //
call D2::f(int), that is, Base::f(int)
Base& br = d
br.f(1); // call Base::f(int)
}

This is a simple consequence of a class also being considered a namespace (§16.2).

Several using-declarations can bring in names from multiple base classes. For example:

struct B1 {
void f(int);
};

struct B2 {
void f(double);
};

struct D : B1, B2 {
using B1::f;
using B2::f;
void f(char);
};

void use(D d)
{
d.f(1); //
call D::f(int), that is, B1::f(int)
d.f('a'); // call D::f(char)
d.f(1.0); // call D::f(double), that is, B2::f(double)
}

We can bring constructors into a derived class scope; see §20.3.5.1. A name brought into a derived class scope by a using-declaration has its access determined by the placement of the using-declaration; see §20.5.3. We cannot use using-directives to bring all members of a base class into a derived class.

20.3.5.1. Inheriting Constructors

Say I want a vector that’s just like std::vector, but with guaranteed range checking. I can try this:

template<class T>
struct Vector : std::vector<T> {

T& operator[](size_type i) { check(i); return this–>elem(i); }
const T& operator[](size_type i) const { check(i); return this–>elem(i); }

void check(size_type i) { if (this–>size()<i) throw range_error{"Vector::check() failed"}; }
};

Unfortunately, we would soon find out that this definition is rather incomplete. For example:

Vector<int> v { 1, 2, 3, 5, 8 }; // error: no initializer-list constructor

A quick check will show that Vector failed to inherit any constructors from std::vector.

That’s not an unreasonable rule: if a class adds data members to a base or requires a stricter class invariant, it would be a disaster to inherit constructors. However, Vector did not do anything like that.

We solve the problem by simply saying that the constructors should be inherited:

template<class T>
struct Vector : std::vector<T> {
using vector<T>::vector; //
inherit constructors

T& operator=[](size_type i) { check(i); return this–>elem(i); }
const T& operator=(size_type i) const { check(i); return this–>elem(i); }

void check(size_type i) { if (this–>size()<i) throw Bad_index(i); }
};

Vector<int> v { 1, 2, 3, 5, 8 }; //
OK: use initializer-list constructor from std::vector

This use of using is exactly equivalent to its use for ordinary functions (§14.4.5, §20.3.5).

If you so choose, you can shoot yourself in the foot by inheriting constructors in a derived class in which you define new member variables needing explicit initialization:

struct B1 {
B1(int) { }
};

struct D1 : B1 {
using B1::B1; //
implicitly declares D1(int)
string s; // string has a default constructor
int x; // we "forgot" to provide for initialization of x
};

void test() {
D1 d {6}; //
oops: d.x is not initialized
D1 e; // error: D1 has no default constructor
}

The reason that D1::s is initialized and D1::x is not is that the inheriting constructor is equivalent to a constructor that simply initializes the base. In this case, we might equivalently have written:

struct D1 : B1 {
D1(int i) : B1(i) { }
string s; //
string has a default constructor
int x; // we "forgot" to provide for initialization of x
};

One way to remove the bullet from your foot is by adding an in-class member initializer (§17.4.4):

struct D1 : B1 {
using B1::B1; //
implicitly declares D1(int)
int x {0}; // note: x is initialized
};

void test()
{
D1 d {6}; //
d.x is zero
}

Most often it is best to avoid being clever and restrict the use of inheriting constructors to the simple cases where no data members are added.

20.3.6. Return Type Relaxation

There is a relaxation of the rule that the type of an overriding function must be the same as the type of the virtual function it overrides. That is, if the original return type was B*, then the return type of the overriding function may be D*, provided B is a public base of D. Similarly, a return type of B& may be relaxed to D&. This is sometimes called the covariant return rule.

This relaxation applies only to return types that are pointers or references, and not to “smart pointers” such as unique_ptr5.2.1). In particular, there is not a similar relaxation of the rules for argument types because that would lead to type violations.

Consider a class hierarchy representing different kinds of expressions. In addition to the operations for manipulating expressions, the base class Expr would provide facilities for making new expression objects of the various expression types:

class Expr {
public:
Expr(); //
default constructor
Expr(const Expr&); // copy constructor
virtual Expr* new_expr() =0;
virtual Expr* clone() =0;
//
...
};

The idea is that new_expr() makes a default object of the type of the expression and clone() makes a copy of the object. Both will return an object of some specific class derived from Expr. They can never just return a “plain Expr” because Expr was deliberately and appropriately declared to be an abstract class.

A derived class can override new_expr() and/or clone() to return an object of its own type:

class Cond : public Expr {
public:
Cond();
Cond(const Cond&);
Cond* new_expr() override { return new Cond(); }
Cond* clone() override { return new Cond(*this); }
//
...
};

This means that given an object of class Expr, a user can create a new object of “just the same type.” For example:

void user(Expr* p)
{
Expr* p2 = p–>new_expr();
//
...
}

The pointer assigned to p2 is declared to point to a “plain Expr,” but it will point to an object of a type derived from Expr, such as Cond.

The return type of Cond::new_expr() and Cond::clone() is Cond* rather than Expr*. This allows a Cond to be cloned without loss of type information. Similarly, a derived class Addition would have a clone() returning a Addition*. For example:

void user2(Cond* pc, Addition* pa)
{
Cond* p1 = pc–>clone();
Addition* p2 = pa–>clone();
//
...
}

If we use clone() for an Expr we only know that the result is an Expr*:

void user3(Cond* pc, Expr* pe)
{
Cond* p1 = pc–>clone();
Cond* p2 = pe–>clone(); //
error: Expr::clone() returns an Expr*
// ...
}

Because functions such as new_expr() and clone() are virtual and they (indirectly) construct objects, they are often called virtual constructors. Each simply uses a constructor to create a suitable object.

To make an object, a constructor needs the exact type of the object it is to create. Consequently, a constructor cannot be virtual. Furthermore, a constructor is not quite an ordinary function. In particular, it interacts with memory management routines in ways ordinary member functions don’t. So, you cannot take a pointer to a constructor and pass that to an object creation function.

Both of these restrictions can be circumvented by defining a function that calls a constructor and returns a constructed object. This is fortunate because creating a new object without knowing its exact type is often useful. The Ival_box_maker21.2.4) is an example of a class designed specifically to do that.

20.4. Abstract Classes

Many classes resemble class Employee in that they are useful as themselves, as interfaces for derived classes, and as part of the implementation of derived classes. For such classes, the techniques described in §20.3.2 suffice. However, not all classes follow that pattern. Some classes, such as a class Shape, represent abstract concepts for which objects cannot exist. A Shape makes sense only as the base of some class derived from it. This can be seen from the fact that it is not possible to provide sensible definitions for its virtual functions:

class Shape {
public:
virtual void rotate(int) { throw runtime_error{"Shape::rotate"}; } //
inelegant
virtual void draw() const { throw runtime_error{"Shape::draw"}; }
//
...
};

Trying to make a shape of this unspecified kind is silly but legal:

Shape s; // silly: "shapeless shape"

It is silly because every operation on s will result in an error.

A better alternative is to declare the virtual functions of class Shape to be pure virtual functions. A virtual function is “made pure” by the “pseudo initializer” = 0:

class Shape { // abstract class
public:
virtual void rotate(int) = 0; //
pure virtual function
virtual void draw() const = 0; // pure virtual function
virtual bool is_closed() const = 0; // pure virtual function
// ...
virtual ~Shape(); // virtual
};

A class with one or more pure virtual functions is an abstract class, and no objects of that abstract class can be created:

Shape s; // error: variable of abstract class Shape

An abstract class is intended as an interface to objects accessed through pointers and references (to preserve polymorphic behavior). Consequently, it is usually important for an abstract class to have a virtual destructor (§3.2.4, §21.2.2). Because the interface provided by an abstract class cannot be used to create objects using a constructor, abstract classes don’t usually have constructors.

An abstract class can be used only as an interface to other classes. For example:

class Point { /* ... */ };

class Circle : public Shape {
public:
void rotate(int) override { }
void draw() const override;
bool is_closed() const override { return true; }


Circle(Point p, int r);
private:
Point center;
int radius;
};

A pure virtual function that is not defined in a derived class remains a pure virtual function, so the derived class is also an abstract class. This allows us to build implementations in stages:

class Polygon : public Shape { // abstract class
public:
bool is_closed() const override { return true; }
//
... draw and rotate not overridden ...
};

Polygon b {p1,p2,p3,p4}; // error: declaration of object of abstract class Polygon

Polygon is still abstract because we did not override draw() and rotate(). Only when that is done do we have a class from which we can create objects:

class Irregular_polygon : public Polygon {
list<Point> lp;
public:
Irregular_polygon(initializer_list<Point>);

void draw() const override;
void rotate(int) override;
//
...
};

Irregular_polygon poly {p1,p2,p3,p4}; //
assume that p1 .. p4 are Points defined somewhere

An abstract class provides an interface without exposing implementation details. For example, an operating system might hide the details of its device drivers behind an abstract class:

class Character_device {
public:
virtual int open(int opt) = 0;
virtual int close(int opt) = 0;
virtual int read(char* p, int n) = 0;
virtual int write(const char* p, int n) = 0;
virtual int ioctl(int ...) = 0; //
device I/O control

virtual ~Character_device() { } // virtual destructor
};

We can then specify drivers as classes derived from Character_device and manipulate a variety of drivers through that interface.

The design style supported by abstract classes is called interface inheritance in contrast to the implementation inheritance supported by base classes with state and/or defined member functions. Combinations of the two approaches are possible. That is, we can define and use base classes with both state and pure virtual functions. However, such mixtures of approaches can be confusing and require extra care.

With the introduction of abstract classes, we have the basic facilities for writing a complete program in a modular fashion using classes as building blocks.

20.5. Access Control

A member of a class can be private, protected, or public:

• If it is private, its name can be used only by member functions and friends of the class in which it is declared.

• If it is protected, its name can be used only by member functions and friends of the class in which it is declared and by member functions and friends of classes derived from this class (see §19.4).

• If it is public, its name can be used by any function.

This reflects the view that there are three kinds of functions accessing a class: functions implementing the class (its friends and members), functions implementing a derived class (the derived class’s friends and members), and other functions. This can be presented graphically:

Image

The access control is applied uniformly to names. What a name refers to does not affect the control of its use. This means that we can have private member functions, types, constants, etc., as well as private data members. For example, an efficient nonintrusive list class often requires data structures to keep track of elements. A list is nonintrusive if it does not require modification to its elements (e.g., by requiring element types to have link fields). The information and data structures used to organize the list can be kept private:

template<class T>
class List {
public:
void insert(T);
T get();
//
...
private:
struct Link { T val; Link* next; };

struct Chunk {
enum { chunk_size = 15};
Link v[chunk_size];
Chunk* next;
};

Chunk* allocated;
Link* free;
Link* get_free();
Link* head;
};

The definitions of the public functions are pretty strainghtforward:

template<class T>
void List<T>::insert(T val)
{
Link* lnk = get_free();
lnk–>val = val;
lnk–>next = head;
head = lnk;
}

template<class T>
T List<T>::get()
{
if (head == 0)
throw Underflow{}; //
Underflow is my exception class

Link* p= head;
head = p–>next;
p–>next = free;
free = p;
return p–>val;
}

As is common, the definition of the supporting (here, private) functions are a bit more tricky:

template<class T>
typename List<T>::Link* List<T>::get_free()
{
if (free == 0) {
//
... allocate a new chunk and place its Links on the free list ...
}
Link* p = free;
free = free–>next;
return p;
}

The List<T> scope is entered by saying List<T>:: in a member function definition. However, because the return type of get_free() is mentioned before the name List<T>::get_free() is mentioned, the full name List<T>::Link must be used instead of the abbreviation Link. The alternative is to use the suffix notation for return types (§12.1.4):

template<class T>
auto List<T>::get_free() –> Link*
{
//
...
}

Nonmember functions (except friends) do not have such access:

template<typename T>
void would_be_meddler(List<T>* p)
{
List<T>::Link* q = 0; //
error: List<T>::Link is private
// ...
q = p–>free; // error: List<T>::free is private
// ...
if (List<T>::Chunk::chunk_size > 31) { // error: List<T>::Chunk::chunk_size is private
// ...
}
}

In a class, members are by default private; in a struct, members are by default public16.2.4).

The obvious alternative to using a member type is to place the type in the surrounding namespace. For example:

template<class T>
struct Link2 {
T val;
Link2* next;
};

template<class T>
class List {
private:
Link2<T>* free;
//
...
};

Link is implicitly parameterized with List<T>’s parameter T. For Link2, we must make that explicit.

If a member type does not depend on all the template class’s parameters, the nonmember version can be preferable; see §23.4.6.3.

If the nested class is not generally useful by itself and the enclosing class needs access to its representation, declaring the member class a friend19.4.2) may be a good idea:

template<class T> class List;

template<class T>
class Link3 {
friend class List<T>; //
only List<T> can access Link<T>
T val;
Link3* next;
};

template<class T>
class List {
private:

Link3<T>* free;
//
...
};

A compiler may reorder sections of a class with separate access specifiers (§8.2.6). For example:

class S {
public:
int m1;
public:
int m2;
};

The compiler may decide for m2 to precede m1 in the layout of an S object. Such reordering could come as a surprise to the programmer and is implementation-dependent, so don’t use multiple access specifiers for data members without good reason.

20.5.1. protected Members

When designing a class hierarchy, we sometimes provide functions designed to be used by implementers of derived classes but not by the general user. For example, we may provide an (efficient) unchecked access function for derived class implementers and (safe) checked access for others. Declaring the unchecked version protected achieves that. For example:

class Buffer {
public:
char& operator[](int i); //
checked access
// ...
protected:
char& access(int i); //
unchecked access
// ...
};

class Circular_buffer : public Buffer {
public:
void reallocate(char* p, int s); //
change location and size
// ...
};

void Circular_buffer::reallocate(char* p, int s)// change location and size
{
// ...
for (int i=0; i!=old_sz; ++i)
p[i] = access(i); //
no redundant checking
// ...
}
void f(Buffer& b)
{
b[3] = 'b'; //
OK (checked)
b.access(3) = 'c'; // error: Buffer::access() is protected
}

For another example, see Window_with_border in §21.3.5.2.

A derived class can access a base class’s protected members only for objects of its own type:

class Buffer {
protected:
char a[128];
//
...
};

class Linked_buffer : public Buffer {
// ...
};


class Circular_buffer : public Buffer {
// ...
void f(Linked_buffer* p)
{
a[0] = 0; //
OK: access to Circular_buffer's own protected member
p–>a[0] = 0; // error: access to protected member of different type
}
};

This prevents subtle errors that would otherwise occur when one derived class corrupts data belonging to other derived classes.

20.5.1.1. Use of protected Members

The simple private/public model of data hiding serves the notion of concrete types (§16.3) well. However, when derived classes are used, there are two kinds of users of a class: derived classes and “the general public.” The members and friends that implement the operations on the class operate on the class objects on behalf of these users. The private/public model allows the programmer to distinguish clearly between the implementers and the general public, but it does not provide a way of catering specifically to derived classes.

Members declared protected are far more open to abuse than members declared private. In particular, declaring data members protected is usually a design error. Placing significant amounts of data in a common class for all derived classes to use leaves that data open to corruption. Worse, protected data, like public data, cannot easily be restructured because there is no good way of finding every use. Thus, protected data becomes a software maintenance problem.

Fortunately, you don’t have to use protected data; private is the default in classes and is usually the better choice. In my experience, there have always been alternatives to placing significant amounts of information in a common base class for derived classes to use directly.

However, none of these objections are significant for protected member functions; protected is a fine way of specifying operations for use in derived classes. The Ival_slider in §21.2.2 is an example of this. Had the implementation class been private in this example, further derivation would have been infeasible. On the other hand, making bases providing implementation details public invites mistakes and misuse.

20.5.2. Access to Base Classes

Like a member, a base class can be declared private, protected, or public. For example:

class X : public B { /* ... */ };
class Y : protected B { /*
... */ };
class Z : private B { /*
... */ };

The different access specifiers serve different design needs:

public derivation makes the derived class a subtype of its base. For example, X is a kind of B. This is the most common form of derivation.

private bases are most useful when defining a class by restricting the interface to a base so that stronger guarantees can be provided. For example, B is an implementation detail of Z. The Vector of pointers template that adds type checking to its Vector<void*> base from §25.3 is a good example.

protected bases are useful in class hierarchies in which further derivation is the norm. Like private derivation, protected derivation is used to represent implementation details. The Ival_slider from §21.2.2 is a good example.

The access specifier for a base class can be left out. In that case, the base defaults to a private base for a class and a public base for a struct. For example:

class XX : B { /* ... */ }; // B is a private base
struct YY : B { /* ... */ }; // B is a public base

People expect base classes to be public (that is, to express a subtype relationship), so the absence of an access specifier for a base is likely to be surprising for a class but not for a struct.

The access specifier for a base class controls the access to members of the base class and the conversion of pointers and references from the derived class type to the base class type. Consider a class D derived from a base class B:

• If B is a private base, its public and protected members can be used only by member functions and friends of D. Only friends and members of D can convert a D* to a B*.

• If B is a protected base, its public and protected members can be used only by member functions and friends of D and by member functions and friends of classes derived from D. Only friends and members of D and friends and members of classes derived from D can convert a D* to a B*.

• If B is a public base, its public members can be used by any function. In addition, its protected members can be used by members and friends of D and members and friends of classes derived from D. Any function can convert a D* to a B*.

This basically restates the rules for member access (§20.5). When designing a class, we choose access for bases in the same way as we do for members. For an example, see Ival_slider in §21.2.2.

20.5.2.1. Multiple Inheritance and Access Control

If the name of a base class can be reached through multiple paths in a multiple-inheritance lattice (§21.3), it is accessible if it is accessible through any path. For example:

struct B {
int m;
static int sm;
//
...
};

class D1 : public virtual B { /* ... */ } ;
class D2 : public virtual B { /*
... */ } ;
class D12 : public D1, private D2 { /*
... */ };

D12* pd = new D12;
B* pb = pd; // OK: accessible through D1
int i1 = pd–>m; // OK: accessible through D1

If a single entity is reachable through several paths, we can still refer to it without ambiguity. For example:

class X1 : public B { /* ... */ } ;
class X2 : public B { /*
... */ } ;
class XX : public X1, public X2 { /*
... */ };

XX* pxx = new XX;
int i1 = pxx–>m; //
error, ambiguous: XX::X1::B::m or XX::X2::B::m?
int i2 = pxx–>sm; // OK: there is only one B::sm in an XX (sm is a static member)

20.5.3. using-Declarations and Access Control

A using-declaration (§14.2.2, §20.3.5) cannot be used to gain access to additional information. It is simply a mechanism for making accessible information more convenient to use. On the other hand, once access is available, it can be granted to other users. For example:

class B {
private:
int a;
protected:
int b;
public:
int c;
};

class D : public B {
public:
using B::a; //
error: B::a is private
using B::b; // make B::b publicly available through D
};

When a using-declaration is combined with private or protected derivation, it can be used to specify interfaces to some, but not all, of the facilities usually offered by a class. For example:

class BB : private B { // give access to B::b and B::c, but not B::a
public:
using B::b;
using B::c;
};

See also §20.3.5.

20.6. Pointers to Members

A pointer to member is an offset-like construct that allows a programmer to indirectly refer to a member of a class. The operators –>* and . * are arguably the most specialized and least used C++ operators. Using –>, we can access a member of a class, m, by naming it: p–>m. Using –>*, we can access a member that (conceptually) has its name stored in a pointer to member, ptom: p–> ptom. This allows us to access members with their names passed as arguments. In both cases, p must be a pointer to an object of an appropriate class.

A pointer to member cannot be assigned to a void* or any other ordinary pointer. A null pointer (e.g., nullptr) can be assigned to a pointer to member and then represents “no member.”

20.6.1. Pointers to Function Members

Many classes provide simple, very general interfaces intended to be invoked in several different ways. For example, many “object-oriented” user interfaces define a set of requests to which every object represented on the screen should be prepared to respond. In addition, such requests can be presented directly or indirectly from programs. Consider a simple variant of this idea:

class Std_interface {
public:
virtual void start() = 0;
virtual void suspend() = 0;
virtual void resume() = 0;
virtual void quit() = 0;
virtual void full_size() = 0;
virtual void small() = 0;

virtual ~Std_interface() {}
};

The exact meaning of each operation is defined by the object on which it is invoked. Often, there is a layer of software between the person or program issuing the request and the object receiving it. Ideally, such intermediate layers of software should not have to know anything about the individual operations such as resume() and full_size(). If they did, the intermediate layers would have to be updated each time an operation changed. Consequently, such intermediate layers simply transmit data representing the operation to be invoked from the source of the request to its recipient.

One simple way of doing that is to send a string representing the operation to be invoked. For example, to invoke suspend() we could send the string "suspend". However, someone has to create that string and someone has to decode it to determine to which operation it corresponds – if any. Often, that seems indirect and tedious. Instead, we might simply send an integer representing the operation. For example, 2 might be used to mean suspend(). However, while an integer may be convenient for machines to deal with, it can get pretty obscure for people. We still have to write code to determine that 2 means suspend() and to invoke suspend().

However, we can use a pointer to member to indirectly refer to a member of a class. Consider Std_interface. If I want to invoke suspend() for some object without mentioning suspend() directly, I need a pointer to member referring to Std_interface::suspend(). I also need a pointer or reference to the object I want to suspend. Consider a trivial example:

using Pstd_mem = void (Std_interface::*)(); // pointer-to-member type

void f(Std_interface* p)
{

Pstd_mem s = &Std_interface::suspend; // pointer to suspend()
p–>suspend(); // direct call
p–>*s(); // call through pointer to member
}

A pointer to member can be obtained by applying the address-of operator, &, to a fully qualified class member name, for example, &Std_interface::suspend. A variable of type “pointer to member of class X” is declared using a declarator of the form X::*.

The use of an alias to compensate for the lack of readability of the C declarator syntax is typical. However, please note how the X::* declarator matches the traditional * declarator exactly.

A pointer to member m can be used in combination with an object. The operators –>* and .* allow the programmer to express such combinations. For example, p–>*m binds m to the object pointed to by p, and obj.*m binds m to the object obj. The result can be used in accordance withm’s type. It is not possible to store the result of a –>* or a .* operation for later use.

Naturally, if we knew which member we wanted to call, we would invoke it directly rather than mess with pointers to members. Just like ordinary pointers to functions, pointers to member functions are used when we need to refer to a function without having to know its name. However, a pointer to member isn’t a pointer to a piece of memory the way a pointer to a variable or a pointer to a function is. It is more like an offset into a structure or an index into an array, but of course an implementation takes into account the differences between data members, virtual functions, nonvirtual functions, etc. When a pointer to member is combined with a pointer to an object of the right type, it yields something that identifies a particular member of a particular object.

The p–>*s() call can be represented graphically like this:

Image

Because a pointer to a virtual member (s in this example) is a kind of offset, it does not depend on an object’s location in memory. A pointer to a virtual member can therefore be passed between different address spaces as long as the same object layout is used in both. Like pointers to ordinary functions, pointers to non-virtual member functions cannot be exchanged between address spaces.

Note that the function invoked through the pointer to function can be virtual. For example, when we call suspend() through a pointer to function, we get the right suspend() for the object to which the pointer to function is applied. This is an essential aspect of pointers to functions.

When writing an interpreter, we might use pointers to members to invoke functions presented as strings:

map<string,Std_interface*> variable;
map<string,Pstd_mem> operation;

void call_member(string var, string oper)
{
(variable[var]–>*operation[oper])(); //
var.oper()
}

A static member isn’t associated with a particular object, so a pointer to a static member is simply an ordinary pointer. For example:

class Task {
// ...
static void schedule();
};

void (*p)() = &Task::schedule; //
OK
void (Task::* pm)() = &Task::schedule; // error: ordinary pointer assigned
// to pointer to member

Pointers to data members are described in §20.6.2.

20.6.2. Pointers to Data Members

Naturally, the notion of pointer to member applies to data members and to member functions with arguments and return types. For example:

struct C {
const char* val;
int i;


void print(int x) { cout << val << x << '\n'; }
int f1(int);
void f2();
C(const char* v) { val = v; }
};

using Pmfi = void (C::*)(int); //
pointer to member function of C taking an int
using Pm = const char* C::* // pointer to char* data member of C
void f(C& z1, C& z2)
{
C* p = &z2;
Pmfi pf = &C::print;
Pm pm = &C::val;

z1.print(1);
(z1.*pf)(2);
z1.*pm = "nv1 ";
p–>*pm = "nv2 ";
z2.print(3);
(p–>*pf)(4);

pf = &C::f1; //
error: return type mismatch
pf = &C::f2; // error: argument type mismatch
pm = &C::i; // error: type mismatch
pm = pf; // error: type mismatch
}

The type of a pointer to function is checked just like any other type.

20.6.3. Base and Derived Members

A derived class has at least the members that it inherits from its base classes. Often it has more. This implies that we can safely assign a pointer to a member of a base class to a pointer to a member of a derived class, but not the other way around. This property is often called contravariance. For example:

class Text : public Std_interface {
public:
void start();
void suspend();
//
...
virtual void print();
private:
vector s;
};

void (Std_interface::* pmi)() = &Text::print; //
error
void (Text::*pmt)() = &Std_interface::start; // OK

This contravariance rule appears to be the opposite of the rule that says we can assign a pointer to a derived class to a pointer to its base class. In fact, both rules exist to preserve the fundamental guarantee that a pointer may never point to an object that doesn’t at least have the properties that the pointer promises. In this case, Std_interface::* can be applied to any Std_interface, and most such objects presumably are not of type Text. Consequently, they do not have the member Text::print with which we tried to initialize pmi. By refusing the initialization, the compiler saves us from a run-time error.

20.7. Advice

[1] Avoid type fields; §20.3.1.

[2] Access polymorphic objects through pointers and references; §20.3.2.

[3] Use abstract classes to focus design on the provision of clean interfaces; §20.4.

[4] Use override to make overriding explicit in large class hierarchies; §20.3.4.1.

[5] Use final only sparingly; §20.3.4.2.

[6] Use abstract classes to specify interfaces; §20.4.

[7] Use abstract classes to keep implementation details out of interfaces; §20.4.

[8] A class with a virtual function should have a virtual destructor; §20.4.

[9] An abstract class typically doesn’t need a constructor; §20.4.

[10] Prefer private members for implementation details; §20.5.

[11] Prefer public members for interfaces; §20.5.

[12] Use protected members only carefully when really needed; §20.5.1.1.

[13] Don’t declare data members protected; §20.5.1.1.