派生类
任何类类型(无论是用 class-key class
还是 struct
声明)都可以声明为从一个或多个 基类 派生,而这些基类 wiederum 可以从它们自己的基类派生,从而形成一个继承层次结构。
内容 |
[编辑] 语法
基类的列表在 类声明语法 的 base-clause 中提供。 base-clause 由字符 :
后跟一个或多个以逗号分隔的 base-specifier 组成。
attr (可选) class-or-computed | (1) | ||||||||
attr (可选) virtual class-or-computed |
(2) | ||||||||
attr (可选) access-specifier class-or-computed | (3) | ||||||||
attr (可选) virtual access-specifier class-or-computed |
(4) | ||||||||
attr (可选) access-specifier virtual class-or-computed |
(5) | ||||||||
virtual
和 access-specifier 可以以任何顺序出现。attr | - | (自 C++11 起) 任意数量的 属性 序列 | ||||
access-specifier | - | private 、public 或 protected 之一 | ||||
class-or-computed | - | 以下之一
|
由于语法限制,详细类型说明符 无法直接作为 class-or-computed 出现。
base-clause 中的 base-specifier 可以是 包展开。 用 |
(自 C++11 起) |
如果省略了 access-specifier,则对于用 class-key struct
声明的派生类,它默认为 public
;对于用 class-key class
声明的派生类,它默认为 private
。
struct Base { int a, b, c; }; // every object of type Derived includes Base as a subobject struct Derived : Base { int b; }; // every object of type Derived2 includes Derived and Base as subobjects struct Derived2 : Derived { int c; };
在 base-clause 中列出的 class-or-computed 表示的类是直接基类。它们的基类是间接基类。同一个类不能被多次指定为直接基类,但同一个类可以是直接基类和间接基类。
每个直接和间接基类都以 _基类子对象_ 的形式存在于派生类的对象表示中,位于 ABI 相关的偏移量处。空基类通常不会增加派生对象的大小,因为它们会进行 _空基优化_ 。基类子对象的构造函数由派生类的构造函数调用:可以在 _成员初始化列表_ 中为这些构造函数提供参数。
[编辑] 虚基类
对于每个指定为 virtual
的不同基类,最派生对象只包含一个该类型的基类子对象,即使该类在继承层次结构中出现多次(只要每次都以 virtual
方式继承)。
struct B { int n; }; class X : public virtual B {}; class Y : virtual public B {}; class Z : public B {}; // every object of type AA has one X, one Y, one Z, and two B's: // one that is the base of Z and one that is shared by X and Y struct AA : X, Y, Z { AA() { X::n = 1; // modifies the virtual B subobject's member Y::n = 2; // modifies the same virtual B subobject's member Z::n = 3; // modifies the non-virtual B subobject's member std::cout << X::n << Y::n << Z::n << '\n'; // prints 223 } };
包含虚基类的继承层次结构的示例是标准库的 iostreams 层次结构: std::istream 和 std::ostream 使用虚继承派生自 std::ios 。 std::iostream 派生自 std::istream 和 std::ostream ,因此 std::iostream 的每个实例都包含一个 std::ostream 子对象,一个 std::istream 子对象,以及只有一个 std::ios 子对象(因此,只有一个 std::ios_base )。
所有虚基类子对象都在任何非虚基类子对象之前初始化,因此只有最派生类在其 _成员初始化列表_ 中调用虚基类的构造函数。
struct B { int n; B(int x) : n(x) {} }; struct X : virtual B { X() : B(1) {} }; struct Y : virtual B { Y() : B(2) {} }; struct AA : X, Y { AA() : B(3), X(), Y() {} }; // the default constructor of AA calls the default constructors of X and Y // but those constructors do not call the constructor of B because B is a virtual base AA a; // a.n == 3 // the default constructor of X calls the constructor of B X x; // x.n == 1
当涉及虚继承时,对于类成员的非限定名称查找存在 _特殊规则_ (有时称为支配规则)。
[编辑] 公有继承
当一个类使用 public
_成员访问说明符_ 从基类派生时,基类的所有公有成员都可以作为派生类的公有成员访问,基类的所有受保护成员都可以作为派生类的受保护成员访问(基类的私有成员永远不可访问,除非被声明为友元)。
公有继承模拟了面向对象编程的子类型关系:派生类对象 IS-A 基类对象。指向派生对象的引用和指针应该可以被任何期望指向其任何公有基类的引用或指针的代码使用(参见 _LSP_ ),或者,用 _DbC_ 术语来说,派生类应该维护其公有基类的类不变量,不应该加强任何前提条件或削弱任何它 _重写_ 的成员函数的后置条件。
#include <iostream> #include <string> #include <vector> struct MenuOption { std::string title; }; // Menu is a vector of MenuOption: options can be inserted, removed, reordered... // and has a title. class Menu : public std::vector<MenuOption> { public: std::string title; void print() const { std::cout << title << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) std::cout << " " << (i + 1) << ". " << at(i).title << '\n'; } }; // Note: Menu::title is not problematic because its role is independent of the base class. enum class Color { WHITE, RED, BLUE, GREEN }; void apply_terminal_color(Color) { /* OS-specific */ } // THIS IS BAD! // ColorMenu is a Menu where every option has a custom color. class ColorMenu : public Menu { public: std::vector<Color> colors; void print() const { std::cout << title << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) { std::cout << " " << (i + 1) << ". "; apply_terminal_color(colors[i]); std::cout << at(i).title << '\n'; apply_terminal_color(Color::WHITE); } } }; // ColorMenu needs the following invariants that cannot be satisfied // by publicly inheriting from Menu, for example: // - ColorMenu::colors and Menu must have the same number of elements // - To make sense, calling erase() should remove also elements from colors, // in order to let options keep their colors // Basically every non-const call to a std::vector method will break the invariant // of the ColorMenu and will need fixing from the user by correctly managing colors. int main() { ColorMenu color_menu; // The big problem of this class is that we must keep ColorMenu::Color // in sync with Menu. color_menu.push_back(MenuOption{"Some choice"}); // color_menu.print(); // ERROR! colors[i] in print() is out of range color_menu.colors.push_back(Color::RED); color_menu.print(); // OK: colors and Menu has the same number of elements }
[编辑] 受保护继承
当一个类使用 protected
_成员访问说明符_ 从基类派生时,基类的所有公有和受保护成员都可以作为派生类的受保护成员访问(基类的私有成员永远不可访问,除非被声明为友元)。
受保护继承可用于 "受控多态性":在派生类的成员以及所有进一步派生类的成员中,派生类 IS-A 基类:指向派生类的引用和指针可以在期望指向基类的引用和指针的地方使用。
[编辑] 私有继承
当一个类使用 private
_成员访问说明符_ 从基类派生时,基类的所有公有和受保护成员都可以作为派生类的私有成员访问(基类的私有成员永远不可访问,除非被声明为友元)。
私有继承通常用于基于策略的设计,因为策略通常为空类,使用它们作为基类既可以实现静态多态性,又可以利用 _空基优化_ 。
私有继承也可以用来实现组合关系(基类子对象是派生类对象的实现细节)。使用成员可以提供更好的封装,通常是首选,除非派生类需要访问基类的受保护成员(包括构造函数),需要重写基类的虚成员,需要在其他一些基类子对象之前构造并之后析构,需要共享一个虚基类,或者需要控制虚基类的构造。在从 _参数包_ 多重继承的情况下,或者当基类的身份通过模板元编程在编译时确定时,使用成员来实现组合也不适用。
与受保护继承类似,私有继承也可以用于受控多态性:在派生类的成员中(但不在进一步派生的类中),派生类 IS-A 基类。
template<typename Transport> class service : private Transport // private inheritance from the Transport policy { public: void transmit() { this->send(...); // send using whatever transport was supplied } }; // TCP transport policy class tcp { public: void send(...); }; // UDP transport policy class udp { public: void send(...); }; service<tcp> service(host, port); service.transmit(...); // send over TCP
[编辑] 成员名称查找
类成员的非限定和限定名称查找规则在 _名称查找_ 中详细说明。
[编辑] 关键字
[编辑] 缺陷报告
以下行为变更缺陷报告被追溯应用于之前发布的 C++ 标准。
DR | 应用于 | 已发布的行为 | 正确行为 |
---|---|---|---|
CWG 1710 | C++98 | 的语法使得从 需要模板限定符的依赖类 |
允许 |