A class can derive from one or more base classes [class.derived]. Each object of the derived class contains a base class subobject for each base. A class may be a polymorphic class, with virtual functions chosen by the object’s actual type at run time.
Derivation
base-clause: : base-specifier-list
base-specifier: [attribute-specifier-seq] class-or-decltype
[attribute-specifier-seq] virtual [access-specifier] class-or-decltype
[attribute-specifier-seq] access-specifier [virtual] class-or-decltype
The base access limits how far the base’s members are visible in the derived class
[class.access.base]. class defaults to private and struct
to public.
| Base access | A public base member becomes | A protected base member becomes |
|---|---|---|
public | public | protected |
protected | protected | protected |
private | private | private |
A base’s private members are inherited but not accessible. A derived-to-base
pointer or reference converts implicitly where the base is accessible and unambiguous
[conv.ptr].
Assigning a derived object to a base object copies only the base part, which
is called slicing.
Name hiding
A name in the derived class hides every base member with that name, including all overloads, regardless of parameters [class.member.lookup]. A using-declaration brings them back.
struct Base {
void f(int);
void f(double);
};
struct Derived : Base {
void f(char); // hides both Base::f overloads
using Base::f; // now Derived has all three
};
Virtual functions
A virtual function is resolved at run time, by the dynamic type of the object [class.virtual].
Declaring a function with the same name, parameter types, cv-qualifiers and ref-qualifier as
a base class virtual function overrides it, and it is then virtual whether or not
you write virtual.
struct Shape {
virtual ~Shape() = default;
virtual double area() const = 0; // pure virtual
virtual const char* name() const { return "shape"; }
};
struct Circle final : Shape {
double r;
double area() const override { return 3.14159 * r * r; }
const char* name() const override { return "circle"; }
};
std::unique_ptr<Shape> s = std::make_unique<Circle>();
s->name(); // "circle": chosen at run time
s->Shape::name(); // "shape": a qualified call is not virtual
override on the derived functions| Specifier | Effect | Since |
|---|---|---|
virtual | Introduces or marks a virtual function | C++98 |
override | Requires that the function overrides a base virtual function | C++11 |
final | Prevents further overriding, or further derivation on a class | C++11 |
= 0 | Makes the function pure | C++98 |
- The return type must match, or be covariant: a pointer or reference to a class derived from the base’s return class, with no more cv-qualification.
- A
staticfunction cannot be virtual. A constructor cannot be virtual. A template member function cannot be virtual. - Default arguments are chosen by the static type, not the dynamic type, so an override does not change which default is used.
- Access is checked against the static type too, and an override may have a different access from the function it overrides.
- During construction and destruction, a virtual call dispatches to the class currently being constructed or destroyed, not to a more derived one [class.cdtor].
Virtual destructors
Deleting a derived object through a pointer to its base is undefined behaviour unless
the base’s destructor is virtual [expr.delete]. So any class meant to be used
polymorphically should have one, as Shape does above.
Vtables at language level
The standard does not mention vtables. It defines only behaviour: a call to a virtual function chooses the final overrider for the object’s dynamic type. The usual implementation, used by GCC, Clang and MSVC, is as follows.
- Each polymorphic class has a static table, the vtable, with one entry for each virtual function, holding the address of the final overrider.
- Each object has a hidden pointer, the vptr, to the vtable of its class. Constructors and destructors set it, which is why a virtual call during construction resolves to the current class.
- A virtual call loads the vptr, reads an entry and calls it.
- The layout of these tables belongs to the platform ABI: the Itanium C++ ABI on most Unix-like systems, and Microsoft’s on Windows.
dynamic_cast and typeid use run-time type information
reached from the same hidden pointer. See
Expressions.
Abstract classes
A class with at least one pure virtual function, declared = 0, is
abstract [class.abstract]. An abstract class cannot be instantiated as a complete
object. A pointer or reference to it is allowed, and it can be a base class. A derived
class remains abstract until it overrides every pure virtual function.
- A pure virtual function can have a definition, called by a qualified call.
- A pure virtual destructor is allowed, but must be defined, because derived destructors call it.
- Calling a pure virtual function during construction or destruction, by a virtual call, is undefined behaviour.
Multiple inheritance
A class may have several direct bases [class.mi]. Each is a separate subobject. A name found in more than one base is ambiguous, and must be qualified.
struct A { int value; };
struct B { int value; };
struct C : A, B { };
C c;
// c.value; // error: ambiguous
c.A::value = 1; // qualified
Converting a pointer to a second or later base may change its numeric value, since each base subobject is at a different offset.
Virtual base classes
With a virtual base, there is only one shared subobject of that base, however
many paths reach it. This solves the “diamond”.
struct Top { int x; explicit Top(int v) : x(v) { } };
struct Left : virtual Top { Left() : Top(1) { } };
struct Right : virtual Top { Right() : Top(2) { } };
struct Bottom : Left, Right {
Bottom() : Top(3) { } // the most derived class initialises Top
};
Bottom b; // b.x is 3, and there is only one Top
Top in Left and Right are ignored in a BottomVirtual bases are constructed first and destroyed last, and only the most derived class initialises them [class.base.init]. Reaching a virtual base usually costs an extra indirection.
Inheriting constructors
using Base::Base; (C++11) makes the base class’s constructors usable to
initialise the derived class [namespace.udecl], [class.inhctor.init]. Default, copy and move constructors
are not inherited. Members of the derived class are initialised as usual, by their default member
initialisers, or else default-initialised.
struct Base {
Base(int);
Base(int, double);
};
struct Derived : Base {
using Base::Base; // Derived(int) and Derived(int, double) now work
int extra = 0;
};