派生クラス
任意のクラス型(class-key class または struct のいずれかで宣言されたもの)は、1つ以上の基底クラスから派生するように宣言できます。基底クラスもまた、それ自身の基底クラスから派生することができ、継承階層を形成します。
目次 |
[編集] 構文
基底クラスのリストは、クラス宣言構文の base-clause で提供されます。base-clause は文字 : の後に、コンマで区切られた1つ以上の 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 | - | 以下のいずれか
|
elaborated type specifier は、構文の制約のため、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 継承される限り)、その型の基底クラスサブオブジェクトを1つだけ含みます。
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 サブオブジェクト、そしてたった1つの std::ios サブオブジェクト(結果として1つの 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 継承
クラスが public メンバアクセス指定子を使用して基底クラスから派生する場合、基底クラスのすべての public メンバは派生クラスの public メンバとしてアクセス可能であり、基底クラスのすべての protected メンバは派生クラスの protected メンバとしてアクセス可能になります(基底クラスの private メンバは、フレンドにされない限り決してアクセス可能になりません)。
public 継承は、オブジェクト指向プログラミングのサブタイピング関係をモデル化します。派生クラスオブジェクトは基底クラスオブジェクト「である」(IS-A)。派生オブジェクトへの参照およびポインタは、その public 基底クラスのいずれかへの参照またはポインタを期待するコードによって使用可能であることが期待されます(LSP参照)。あるいは、DbCの用語では、派生クラスは public 基底クラスのクラス不変条件を維持し、オーバーライドするメンバ関数の事前条件を強化したり、事後条件を弱めたりしてはなりません。
#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 継承
クラスが protected メンバアクセス指定子を使用して基底クラスから派生する場合、基底クラスのすべての public および protected メンバは派生クラスの protected メンバとしてアクセス可能になります(基底クラスの private メンバは、フレンドにされない限り決してアクセス可能になりません)。
protected 継承は「制御されたポリモーフィズム」に使用される場合があります。派生クラスのメンバ内、およびそれ以降のすべての派生クラスのメンバ内では、派生クラスは基底クラス「である」(IS-A)と見なされます。Base への参照およびポインタが期待される場所で、Derived への参照およびポインタを使用できます。
[編集] private 継承
クラスが private メンバアクセス指定子を使用して基底クラスから派生する場合、基底クラスのすべての public および protected メンバは派生クラスの private メンバとしてアクセス可能になります(基底クラスの private メンバは、フレンドにされない限り決してアクセス可能になりません)。
private 継承は、ポリシーは通常空のクラスであり、それらを基底クラスとして使用することで静的ポリモーフィズムを可能にし、空基底最適化を活用できるため、ポリシーベース設計で一般的に使用されます。
private 継承は、コンポジション関係を実装するためにも使用できます(基底クラスサブオブジェクトは派生クラスオブジェクトの実装詳細です)。メンバを使用する方がより良いカプセル化を提供し、通常は推奨されます。ただし、派生クラスが基底クラスの protected メンバ(コンストラクタを含む)にアクセスする必要がある場合、基底クラスの仮想メンバをオーバーライドする必要がある場合、基底クラスを他の基底サブオブジェクトよりも前に構築し、後に破棄する必要がある場合、仮想基底を共有する必要がある場合、または仮想基底の構築を制御する必要がある場合は除きます。パラメータパックからの多重継承の場合、または基底クラスの識別がテンプレートメタプログラミングによってコンパイル時に決定される場合にも、コンポジションの実装にメンバを使用することは適用できません。
protected 継承と同様に、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 | class-or-decltype の構文では、 template 曖昧性解消子が必要な依存クラスから派生することが不可能でした。 |
template を許可 |