派生類
任何類型別(無論使用 class-key class 或 struct 宣告)都可以宣告為派生自一個或多個基類,而基類又可以派生自它們自己的基類,從而形成一個繼承層次結構。
目錄 |
[編輯] 語法
基類列表在 類宣告語法 的 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 可以以任何順序出現。屬性 | - | (C++11 起) 任意數量的屬性序列 | ||||
access-specifier | - | 以下之一:private、public 或 protected | ||||
class-or-computed | - | 以下之一
|
由於語法限制,elaborated type specifier 不能直接作為 class-or-computed 出現。
base-clause 中的 base-specifiers 可以是 包展開。 宣告為 |
(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++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 1710 | C++98 | class-or-decltype 的語法使得無法從 需要 template 消歧符的從屬類派生 |
允許 template |