Derived classes
任意のクラス型( class-key class または struct で宣言されているかどうかを問わず)は、1つ以上の 基底クラス から 派生 していると宣言でき、これらの基底クラスはさらに独自の基底クラスから派生可能であり、これにより継承階層が形成されます。
目次 |
構文
基底クラスのリストは、
base-clause
によって
class declaration syntax
で提供されます。
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-specifier の base-clause 内では パック展開 を使用できます。
|
(C++11以降) |
access-specifier が省略された場合、 class-key struct で宣言された派生クラスではデフォルトで public となり、 class-key class で宣言された派生クラスではデフォルトで private となります。
struct Base { int a, b, c; }; // Derived型のすべてのオブジェクトはBaseをサブオブジェクトとして含む struct Derived : Base { int b; }; // Derived2型のすべてのオブジェクトはDerivedとBaseをサブオブジェクトとして含む struct Derived2 : Derived { int c; };
class-or-computed で示されるクラスは、 base-clause に列挙された直接基底クラスです。それらの基底クラスは間接基底クラスとなります。同じクラスを直接基底クラスとして複数回指定することはできませんが、同じクラスが直接基底クラスと間接基底クラスの両方になることは可能です。
各直接および間接基底クラスは、派生クラスのオブジェクト表現内で、ABI依存のオフセット位置に 基底クラス部分オブジェクト として存在します。空の基底クラスは通常、 empty base optimization により派生オブジェクトのサイズを増加させません。基底クラス部分オブジェクトのコンストラクタは派生クラスのコンストラクタによって呼び出され、それらのコンストラクタへの引数は member initializer list で指定できます。
仮想基底クラス
それぞれの異なる基底クラスが virtual として指定されている場合、 most derived object はその型の基底クラス部分オブジェクトをただ一つだけ含みます。たとえそのクラスが継承階層内で何度も現れる場合でも(それが毎回 virtual 継承されている限り)。
struct B { int n; }; class X : public virtual B {}; class Y : virtual public B {}; class Z : public B {}; // AA型のすべてのオブジェクトは、1つのX、1つのY、1つのZ、および2つのBを持つ: // 1つはZの基底であり、もう1つはXとYによって共有される struct AA : X, Y, Z { AA() { X::n = 1; // 仮想B部分オブジェクトのメンバを変更 Y::n = 2; // 同じ仮想B部分オブジェクトのメンバを変更 Z::n = 3; // 非仮想B部分オブジェクトのメンバを変更 std::cout << X::n << Y::n << Z::n << '\n'; // 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 サブオブジェクト)を含みます。
すべての仮想基底サブオブジェクトは、非仮想基底サブオブジェクトよりも前に初期化されます。したがって、 most derived class のみがその member initializer list 内で仮想基底クラスのコンストラクタを呼び出します:
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() {} }; // AAのデフォルトコンストラクタはXとYのデフォルトコンストラクタを呼び出す // しかしBが仮想基底クラスであるため、それらのコンストラクタはBのコンストラクタを呼び出さない AA a; // a.n == 3 // XのデフォルトコンストラクタはBのコンストラクタを呼び出す X x; // x.n == 1
仮想継承が関与する場合のクラスメンバに対する 特殊な規則 が存在します(優勢の規則と呼ばれることもあります)。
パブリック継承
クラスが public メンバーアクセス指定子 を使用して基底クラスから派生する場合、基底クラスのすべてのpublicメンバーは派生クラスのpublicメンバーとしてアクセス可能であり、基底クラスのすべてのprotectedメンバーは派生クラスのprotectedメンバーとしてアクセス可能です(基底クラスのprivateメンバーは、フレンド指定されていない限り決してアクセスできません)。
公開継承は、オブジェクト指向プログラミングのサブタイピング関係をモデル化します:派生クラスのオブジェクトは基底クラスのオブジェクトである(IS-A関係)。派生オブジェクトへの参照とポインタは、その公開基底のいずれかへの参照またはポインタを期待する任意のコードで使用できることが期待されます( LSP を参照)。あるいは DbC の用語では、派生クラスはその公開基底のクラス不変条件を維持し、 override するメンバー関数の事前条件を強化したり事後条件を弱めたりしてはなりません。
#include <iostream> #include <string> #include <vector> struct MenuOption { std::string title; }; // MenuはMenuOptionのベクター:オプションの挿入、削除、並べ替えが可能... // そしてタイトルを持つ 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'; } }; // 注: Menu::titleは基底クラスとは独立した役割を持つため問題ない enum class Color { WHITE, RED, BLUE, GREEN }; void apply_terminal_color(Color) { /* OS固有 */ } // これは悪い例! // ColorMenuは各オプションがカスタムカラーを持つMenu 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は以下の不変条件を必要とするが、Menuからの公開継承では満たせない // 例: // - ColorMenu::colorsとMenuは同じ要素数でなければならない // - 意味をなすためには、erase()の呼び出しはcolorsからも要素を削除し、 // オプションが色を保持できるようにする必要がある // 基本的にstd::vectorメソッドへの非const呼び出しはすべてColorMenuの // 不変条件を破り、ユーザーがcolorsを正しく管理することで修正する必要がある int main() { ColorMenu color_menu; // このクラスの大きな問題は、ColorMenu::colorsをMenuと同期させ続けなければならないこと color_menu.push_back(MenuOption{"Some choice"}); // color_menu.print(); // エラー! print()内のcolors[i]が範囲外 color_menu.colors.push_back(Color::RED); color_menu.print(); // OK: colorsとMenuの要素数が同じ }
プロテクテッド継承
クラスが protected メンバアクセス指定子 を使用して基底クラスから継承する場合、基底クラスのすべてのpublicおよびprotectedメンバは、派生クラスのprotectedメンバとしてアクセス可能になります(基底クラスのprivateメンバは、フレンド指定されていない限り決してアクセスできません)。
プロテクテッド継承は「制御されたポリモーフィズム」に使用される可能性があります:Derivedのメンバー内、およびすべてのさらに派生したクラスのメンバー内では、派生クラスは基底クラスのIS-A関係を持ちます:Derivedへの参照とポインタは、Baseへの参照とポインタが期待される場所で使用できます。
プライベート継承
クラスが private member access specifier を使用して基底クラスから継承する場合、基底クラスのすべてのpublicおよびprotectedメンバは、派生クラスのprivateメンバとしてアクセス可能になります(基底クラスのprivateメンバは、friend指定されない限り決してアクセスできません)。
プライベート継承は、ポリシーベース設計で一般的に使用されます。ポリシーは通常空のクラスであり、それらを基底クラスとして使用することで、静的ポリモーフィズムを可能にし、 empty-base optimization を活用できます。
プライベート継承は、合成関係を実装するためにも使用できます(基底クラスのサブオブジェクトは派生クラスオブジェクトの実装詳細です)。メンバーを使用する方がより優れたカプセル化を提供し、一般的に推奨されます。ただし、派生クラスが基底クラスのprotectedメンバー(コンストラクタを含む)へのアクセスを必要とする場合、基底クラスの仮想メンバーをオーバーライドする必要がある場合、他の基底サブオブジェクトよりも前に基底クラスを構築し、後に破棄する必要がある場合、仮想基底クラスを共有する必要がある場合、または仮想基底クラスの構築を制御する必要がある場合は除きます。メンバーを使用した合成の実装は、 parameter pack からの多重継承の場合や、基底クラスの識別がテンプレートメタプログラミングを通じてコンパイル時に決定される場合にも適用できません。
保護継承と同様に、プライベート継承も制御されたポリモーフィズムに使用できます:派生クラスのメンバ内(ただし、さらに派生したクラス内では除く)では、derived IS-A baseとなります。
template<typename Transport> class service : private Transport // Transportポリシーからのプライベート継承 { public: void transmit() { this->send(...); // 提供されたトランスポートを使用して送信 } }; // TCPトランスポートポリシー class tcp { public: void send(...); }; // UDPトランスポートポリシー class udp { public: void send(...); }; service<tcp> service(host, port); service.transmit(...); // TCP経由で送信
メンバー名検索
クラスメンバーの修飾名および非修飾名のルックアップ規則については、 名前検索 で詳細に説明されています。
キーワード
不具合報告
以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。
| DR | 適用対象 | 公開時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 1710 | C++98 |
class-or-decltype
の構文により、
依存クラスからの派生が不可能であった template ディスアンビギュエータが必要な場合 |
template の使用を許可 |