派生クラス
あらゆるクラス型 (class
または struct
いずれの class-key を用いて宣言されたかにかかわらず) は、ひとつ以上の基底クラスから派生するように宣言することができ、その基底クラスもまた、それ自身の基底クラスから派生することができ、これによって継承階層が形成されます。
基底クラスのリストはクラス宣言の構文の base-clause で提供されます。 base-clause は文字 :
に続くコンマ区切りのひとつ以上の base-specifier のリストから構成されます。
attr(オプション)access-specifier(オプション)virtual-specifier(オプション)class-or-decltype | |||||||||
attr(C++11) | - | オプショナルな任意個の属性の並び。 |
access-specifier | - | private 、 public 、または protected のいずれか。 |
virtual-specifier | - | キーワード virtual 。 |
virtual-specifier と access-specifier は任意の順序で現れて構いません。
base-clause 内の base-specifier はパック展開でも構いません。 final 宣言されたクラスまたは構造体は base-clause に現れることはできません。 | (C++11以上) |
access-specifier を省略した場合は、デフォルトでクラスキー struct
を用いて宣言されたクラスの場合は public
、クラスキー class
を用いて宣言されたクラスの場合は private
になります。
struct Base {int a, b, c;};// Derived 型のすべてのオブジェクトは部分オブジェクトとして Base を含みます。struct Derived : Base {int b;};// Derived2 型のすべてのオブジェクトは部分オブジェクトとして Derived と Base を含みます。struct Derived2 : Derived {int c;};
base-clause に記載されたクラスは直接基底クラスです。 基底クラスの基底クラスは間接基底クラスです。 同じクラスを直接基底クラスとして複数回指定することはできませんが、同じクラスが直接基底クラスと間接基底クラスの両方であることはできます。
それぞれの直接および間接基底クラスは派生クラスのオブジェクト表現内の処理系定義の位置に基底部分オブジェクトとして存在します。 空の基底クラスは通常は空の基底の最適化により派生オブジェクトのサイズを増加させません。 基底クラス部分オブジェクトのコンストラクタは派生クラスのコンストラクタによって呼ばれます。 引数はメンバ初期化子リストでそれらのコンストラクタに提供できます。
目次 |
[編集]仮想基底クラス
virtual
指定された個別の基底クラスそれぞれについて、たとえそのクラスが継承階層内に何度も出現している場合でも、 (そのすべてが virtual
継承している限り) 最も派生したオブジェクトがその型の基底クラス部分オブジェクトをひとつだけ含みます。
struct B {int n;};class X :publicvirtual B {};class Y :virtualpublic B {};class Z :public B {};// AA 型のすべてのオブジェクトは、 X をひとつ、 Y をひとつ、 Z をひとつ、および B を2つ持ちます。// B のひとつは Z の基底であり、もうひとつは 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」を表示します。}};
仮想基底クラスを持つ継承階層の例は、標準ライブラリの iostream の階層です。 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(){}}; // AA のデフォルトコンストラクタは X と Y のデフォルトコンストラクタを呼びますが、// B は仮想基底であるため、 X と Y のコンストラクタは B のコンストラクタを呼びません。 AA a;// a.n == 3// X のデフォルトコンストラクタは B のコンストラクタを呼びます。 X x;// x.n == 1
仮想継承が関わるときのクラスメンバに対する非修飾名の名前探索に対する特別なルールがあります (優勢の規則と呼ばれることもあります)。 メンバ関数定義 を参照してください。
[編集]パブリック継承
クラスが基底から派生するために public
メンバアクセス指定子を用いたとき、その基底クラスのすべてのパブリックメンバは派生クラスのパブリックメンバとしてアクセス可能となり、その基底クラスのすべてのプロテクテッドメンバは派生クラスのプロテクテッドメンバとしてアクセス可能となります (基底のプライベートメンバは、フレンドでなければ、アクセス可能となることはありません)。
パブリック継承はオブジェクト指向プログラミングのサブタイプ関係をモデル化します。 派生クラスのオブジェクトは基底クラスのオブジェクトに対して is-a 関係を持ちます。 派生クラスへの参照およびポインタはそのいずれのパブリック基底への参照またはポインタを期待するいかなるコードによっても使用可能であることが期待されます (リスコフの置換原則を参照してください)。 あるいは、契約プログラミングの用語で言えば、派生クラスはそのパブリック基底のクラスの不変条件を維持するべきであり、オーバーライドするメンバ関数のいかなる事前条件も強めたりいかなる事後条件も弱めたりするべきでありません。
This section is incomplete Reason: typical Shape, Vehicle, and Animal don't do much with invariants. Would be nice to have a a concise example that adjusts contracts correctly: weakens pre and/or strengthens post in the derived class |
[編集]プロテクテッド継承
クラスが基底から継承するために protected
メンバアクセス指定子を用いたとき、その基底クラスのすべてのパブリックメンバおよびプロテクテッドメンバは派生クラスのプロテクテッドメンバとしてアクセス可能になります (基底のプライベートメンバは、フレンドでなければ、アクセス可能となることはありません)。
プロテクテッド継承は「制御された多相性」のために使用できます。 Derived (そこからさらに派生したすべてのクラスでも同様) のメンバ内では、派生クラスは基底に対して is-a 関係を持ちます。 Derived への参照およびポインタは Base への参照およびポインタが期待される場所で使用できます。
[編集]プライベート継承
クラスが基底から派生するために private
メンバアクセス指定子を用いたとき、その基底クラスのすべてのパブリックメンバおよびプロテクテッドメンバは派生クラスのプライベートメンバとしてアクセス可能になります (基底のプライベートメンバは、フレンドでなければ、アクセス可能となることはありません)。
プライベート継承はポリシーベースの設計でよく使用されます。 ポリシーは通常、空のクラスであるため、それらを基底として使用すると、静的ポリモーフィズムを可能しつつ空の基底の最適化を活用できます。
プライベート継承は合成関係 (基底クラス部分オブジェクトは派生クラス部分オブジェクトの実装の詳細である) を実装するためにも使用できます。 メンバを使用する方がより良いカプセル化を提供し一般的には推奨されますが、派生クラスが基底のプロテクテッドメンバ (コンストラクタを含みます) へのアクセスを必要とする、基底の仮想メンバをオーバーライドする必要がある、基底を何らかの他の基底部分オブジェクトより前に構築し後に破棄する必要がある、仮想基底を共有する必要がある、あるいは仮想基底の構築を制御する必要があるなどの場合は除きます。 合成を実装するためのメンバの使用は、パラメータパックから多重継承する場合や、基底クラスの識別性がテンプレートメタプログラミングを通してコンパイル時に決定される場合も、適用できません。
プロテクテッド継承と同様に、プライベート継承も制御された多相性のために使用されることがあります。 派生のメンバ内において (しかしそこからさらに派生したクラス内は含みません)、派生は基底に対して is-a 関係を持ちます。
template<typename Transport>class service : 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 で送信します。
[編集]メンバ名の名前探索
クラスメンバに対する非修飾名および修飾名の名前探索の詳細は名前探索に記載されています。