140 4-1 派生と継承 本節では、既存クラスの資産を継承することによって新しいクラスを作り出す派生について 学習します。 会員クラスの実現 会員 一般会員 ▼ 継 承 4 部 表 考 部 。List 4-1 List 4-2 示 、 。 本来ならば『会員クラス』と呼ぶべきですが、この後で作成する『優待会員クラス』と区別し やすくするために、あえて『一般会員クラス』と呼んでいます。 List 4-1 Member1/Member.h // 一般会員クラス(第1版:ヘッダ部) #ifndef ___Member #define ___Member #include <string> //===== 一般会員クラス =====// class Member { std::string full_name; // 氏名 int number; // 会員番号 double weight; // 体重 public: //--- コンストラクタ ---// Member(const std::string& name, int no, double w); //--- 氏名取得(full_nameのゲッタ)---// std::string name() const { return full_name; } //--- 会員番号取得(numberのゲッタ)---// int no() const { return number; } //--- 体重取得(weightのゲッタ)---// double get_weight() const { return weight; } }; //--- 体重設定(weightのセッタ)---// void set_weight(double w) { weight = (w > 0) ? w : 0; } #endif List 4-2 //--- 一般会員クラス(第1版:ソース部)---// #include <iostream> #include "Member.h" using namespace std; //--- コンストラクタ ---// Member::Member(const string& name, int no, double w) : full_name(name), number(no) { set_weight(w); // 体重を設定 } Member1/Member.cpp 141 実際 数多 存在 、以下 Member ▪氏名 full_name 、 三 ▪会員番号 number 、仮引数 name, no, w 受 取 三 値 、対応 。 関数 定義 。氏名 no、体重 取得・設定 4-1 取得 get_weight 。 set_weight 体重(データメンバ weight)のセッタである set_weight は、weight が負値になるのを避ける ために、仮引数 w に負値を受け取ったときは weight に 0 を代入しています。 関数本体 関数 本体 部 定義 、 部 関数 ▼ 、 定義 中 。 定義 以外 、内部結合 。 コンストラクタでは、名前 full_name と会員番号 number の初期化を青網部のコンストラクタ 初期化子で行っています。また、体重の設定をメンバ関数 set_weight に委ねているため(黒網部) 、 weight が負値になることはありません。 ▼ List 4-3 示 、一般会員 利用 例 。 クラス Member 型のオブジェクトを 1 個生成して、コンストラクタを含む全メンバ関数を適用 するだけの単純なプログラムです。 Member1/MemberTest.cpp List 4-3 //--- 一般会員クラス(第1版)の利用例 ---// 実行結果 #include <iostream> #include "Member.h" No.15:金子健太(71.5kg) using namespace std; int main() { Member kaneko("金子健太", 15, 75.2); double weight = kaneko.get_weight(); kaneko.set_weight(weight - 3.7); // 金子君の体重を取得 // 金子君の体重を更新(3.7kg減量) cout << "No." << kaneko.no() << ":" << kaneko.name() << "(" << kaneko.get_weight() << "kg)\n"; } 一般会員 金子君 金子健太君 15 、金子君 3.7kg 利用 会員番号 確認 体重 更新 。 表 、体重 減量 、 75.2kg Member 型 達成 。体重 kaneko 。 、 71.5kg 関数 get_weight 更新 set_weight 、実行結果 。 派生と継承 、四 name、会員番号 取得 ▼ 一般会員 ▪体重 weight 初期化 full_name, number, weight 他 示 。 142 優待会員クラスの実現 、 、特典 会員一人一人 内容 異 特典 作 、部分的 作成 優待会員 (試作版 ▼ 示 制度 string 型 導入 。 表 優待会員 Member 各 手順 、優待会員 作 追加 変更 部 、便宜的 名 作業 、単純 施 部 。 部 。 、List 4-4 VipMember0 List 4-5 ) 。 プログラムの追加箇所が青網部で、変更箇所が黒網部です。 Member1/VipMember0.h List 4-4 // 試作版・優待会員クラス(ヘッダ部) #ifndef ___VipMember0 #define ___VipMember0 #include <string> //===== 試作版・優待会員クラス class VipMember0 { std::string full_name; // int number; // double weight; // std::string privilege; // =====// 氏名 会員番号 体重 特典 public: //--- コンストラクタ ---// VipMember0(const std::string& name, int no, double w, const std::string& prv); //--- 氏名取得(full_nameのゲッタ)---// std::string name() const { return full_name; } //--- 会員番号取得(numberのゲッタ)---// int no() const { return number; } //--- 体重取得(weightのゲッタ)---// double get_weight() const { return weight; } //--- 体重設定(weightのセッタ)---// void set_weight(double w) { weight = (w > 0) ? w : 0; } //--- 特典取得(privilegeのゲッタ)---// std::string get_privilege() const { return privilege; } }; //--- 特典設定(privilegeのセッタ)---// void set_privilege(const std::string& prv) { privilege = (prv != "") ? prv : "未登録"; } #endif 特典 表 、string 型 伴 、特典 取得・設定 追加 ▼ 継 承 部 『優待会員』 。 一般会員 4 付 privilege 。 get_privilege 追加 set_privilege 。 データメンバ privilege のセッタであるメンバ関数 set_privilege では、仮引数 prv に空文字 列を受け取った場合は、文字列 "未登録" を privilege に代入します。 143 Member1/VipMember0.cpp List 4-5 // 試作版・優待会員クラス(ソース部) #include <string> #include <iostream> #include "VipMember0.h" using namespace std; //--- コンストラクタ ---// VipMember0::VipMember0(const string& name, int no, double w, const string& prv) : full_name(name), number(no) { set_weight(w); // 体重を設定 set_privilege(prv); // 特典を設定 } 変更 、 値 。特典用 設定 処理 文字列 受 追加 取 派生と継承 増 仮引数 。 データメンバ privilege への値の設定は、メンバ関数 set_privilege によって行っています。 ▼ prv 仕様 4-1 そのため、仮引数 prv に空文字列を受け取った場合は、privilege に "未登録" が代入されます。 試作版・優待会員 、 関数 VipMember0 利用 VipMember0 型 適用 例 1 個生成 単純 示 List 4-6 。 、 含 各 。 Member1/VipMember0Test.cpp List 4-6 // 試作版・優待会員クラスの利用例 #include <iostream> #include "VipMember0.h" 実行結果 No.17:峰屋龍次(73.9kg)特典=会費全額免除 using namespace std; int main() { VipMember0 mineya("峰屋龍次", 17, 89.2, "会費全額免除"); double weight = mineya.get_weight(); mineya.set_weight(weight - 15.3); // 峰屋君の体重を取得 // 峰屋君の体重を更新(15.3kg減量) cout << "No." << mineya.no() << ":" << mineya.name() << "(" << mineya.get_weight() << "kg)" << " 特典=" << mineya.get_privilege() << '\n'; } 優待会員 峰屋君 峰屋龍次君 会員番号 、15.3kg 体重 更新 。 17 減量 表 、 、体重 、特典 89.2kg 達成 。体重 VipMember0 型 、 更新 正 "会費全額免除" 関数 get_weight 行 。 mineya set_weight 、実行結果 。 利用 確認 144 、一般会員 作 Member 優待会員 。List 4-7 示 両方 VipMember0 、 一例 利用 。 Member1/SlimOff0.cpp List 4-7 // 一般会員クラス(第1版)と試作版・優待会員クラスの利用例 継 承 4 #include <iostream> #include "Member.h" #include "VipMember0.h" 実行結果 No.15:金子健太(71.5kg) No.17:峰屋龍次(73.9kg)特典=会費全額免除 using namespace std; //--- 一般会員mの減量(体重がdw減る)---// void slim_off(Member& m, double dw) { double weight = m.get_weight(); // 現在の体重を取得 if (weight > dw) m.set_weight(weight - dw); // 体重を更新 } //--- 優待会員mの減量(体重がdw減る)---// void slim_off(VipMember0& m, double dw) { double weight = m.get_weight(); // 現在の体重を取得 if (weight > dw) m.set_weight(weight - dw); // 体重を更新 } int main() { Member kaneko("金子健太", 15, 75.2); VipMember0 mineya("峰屋龍次", 17, 89.2, "会費全額免除"); // 一般会員 // 優待会員 slim_off(kaneko, 3.7); // 金子君が3.7kg減量 cout << "No." << kaneko.no() << ":" << kaneko.name() << "(" << kaneko.get_weight() << "kg)\n"; slim_off(mineya, 15.3); // 峰屋君が15.3kg減量 cout << "No." << mineya.no() << ":" << mineya.name() << "(" << mineya.get_weight() << "kg)" << " 特典=" << mineya.get_privilege() << '\n'; } 二 slim_off 優待会員 同 処理 用 行 、会員 関数 、個別 関数 slim_off 実現 二 同一 低下 。 作 関数 処理対象 変数 似 、何 、 別 関係 、仕様 異 溢 用 。 作 見 非 。一般会員 多重定義 優待会員 類似 似 行 、各 、関数 一般会員 守性 減量処理 関数 、 。 『型』 異 。 、 、 。 別 実現 、 、 開発効率・保 145 派生と継承 問題 解決 手段 存 一 、 関数 作 出 。 関数 追加 書 部 、資産 、 単純 、 Base 、 継承 、 資産 名 Derived 、継承元 Base 新 継承(inheritance) 。 定義中 Derived 網 際 、既 名 継承 4-1 後 Derived 記号 : 派生と継承 。 。 資産 、上書 見 Fig.4-1 定義 、派生 。派生 派生(derive) 続 。 基底クラス名 class Base { int a; int b; public: void func() { /* 中略 */ } }; class Derived : Base { int x; public: void method() { /* 中略 */ } }; 基底クラス(派生の元になるクラス) 派生クラス(基底クラスの資産を継承) Fig.4-1 基底クラスと派生クラス 二 関係 Derived 、派生 、以下 Base 元 表現 派生 。 。 、派生 作 、以下 示 呼 名 、先頭 書 。 ▪派生元 … 基底 /上位 /親 / ▪派生 … 派生 /下位 /子 / 数多 呼 方 存在 、C++ 基底クラス(base class) 、 『 表現 Base Derived ▪ Base ▼ 基底 最 。 派生クラス(derived class) Derived 派生 』 、以下 。 ▪ 派生 世界 、 、 Base 基底 。 Derived 派生 。 、基底 関数 、派生 中 部分 含 資産 継承 、 。 基底クラスで『型』や『列挙定数』などが定義されていれば、それらも資産として継承されます。 146 基底 Base 派生 資産 Derived 概略 図 示 、Fig.4-2 。 // 基底クラス class Base { int a; int b; public: void func() { /* 中略 */ } }; 継 承 4 クラス Derived の資産 クラス Base の資産 a // 派生クラス class Derived : Base { int x; public: void method() { /* 中略 */ } }; b x func method Fig.4-2 基底クラスと派生クラスの資産 図 明 ▪基底 、念 各 資産 確認 。 Base a ▪派生 b 2個 、 関数 1個 func 。 Derived 定義 。 中 、 、 合 x 関数 method 関数 Base 、 3 個、 関数 宣言・定義 継承 2個 、 。 派生クラス(下位クラス)は、基底クラス(上位クラス)の資産を継承するとと 重要 ▼ もに、それを部分として含むクラスである。 コンパイラによって自動的に定義されるデフォルトコンストラクタ・デフォルトデストラク タ・代入演算子も、各クラスの資産として含まれます(この図では省略しています) 。 なお、フレンド関係が派生によって継承されることはありません。 Column 4-1 基底クラスと派生クラス オブジェクト指向プログラミング言語 Java では、派生クラスをサブクラス(sub class)と呼び、 基底クラスをスーパークラス(super class)と呼ぶのが一般的です。 sub とは《部分》という意味で、super とは《部分を含んだ全体・完全》という意味です。 「資産」 4 の量という観点では、基底クラスは派生クラスの《部分》であり、sub や super のニュアンスとは 反対です。このような紛らわしさを避けるため、C++ では、サブクラス/スーパークラスと呼ばずに、 派生クラス/基底クラスと呼んでいます。 147 クラス階層図 派生 、基底 、Fig.4-3 向 生 示 矢印 階層図 結 。矢印 逆向 。 Derived 定義 : Base 親 表明 、勝手 作 ) 供(派生 子供 親(基底 ) 、 情報 子供 矢印 向 知 。 子供 、親 。 』 不可能 →基底 《派生 4-1 派生クラス 、親(基底 ) 、 何人 。基底 宣言 資産の継承 Fig.4-3 クラス階層図と資産の継承 ) 知 基底クラス Derived 。 子供(派生 表 基底 派生 、親 知 Base 、派生 Base 、 。 』 Base 。 階層図 親子関係 派生と継承 『 。 。 向 継承 、資産 子供 側 『 子 私 。 》 、 事情 。 * 、派生 、1 回 。 A 。 。 派生 B B 、 直接 制限 A C 子 、 、 D A 親 C 孫 Fig.4-4 B C D D B 、 考 。 。 直接基底クラス(direct base class) 先祖 子 例 派生 当 呼 、直接 親 間接基底クラス(indirect base class) 呼 、 D B 直接基底 、 A 間接基底 。 、 B 、 『 直接派生 』 D A 表現 。 class A { // … }; 』 、 間接派生 、 『 A class B : A { // … }; A から直接派生 B class C : B { // … }; class D : B { // … }; Fig.4-4 クラス階層図の一例 C D 。 A から間接派生 148 派生の形態 外部 公開 手続 設計 資産 形態 、 、既 学習 大原則 継承 、 基底 派生 依存 非公開 外部 性 ▪ protected 派生 派生 行 、以下 指定 、派生 示 定義 、 Derived 指定子 省略 private 派生 ▼ 、以下 指定子 示 、 。 3種類 場合 、自動 派生 、三 示 行 public 派生 前 。 。 // Baseからpublic派生 chap04/Super.h #ifndef ___Super #define ___Super class Super { private: int pri; protected: int pro; public: int pub; }; Super 派生形態 学習 。 // 非公開 // 限定公開 // 公開 #endif private 派生 Super 置 List 4-8 。 名 // 基底クラスSuper ただし、派生クラスの定義に用いるキーワード 、List 4-8 基底 Base として class ではなく struct を用いて定義した場 合は、 public 派生 となります。 ▼ 4 別問題 関係 class Derived : public Base { /*--- 中略 ---*/ }; 例 4 ▪ public 派生 private, protected, public 派生 基底 4 。 形態 的 、 。派生 公開 内 ▪ private 派生 private 派生 行 例 示 、List 4-9 。 コンパイルエラーとなる行は、// によってコメントアウトしています。なお、プログラムを 実行しても何も表示されません。 private 派生 行 、派生 性 、派生 対 公開 、基底 関数 分 』 、非公開 派生 。 Fig.4-5 不可能 『外部 Super 派生 内部(派生 非公開 基底 示 Sub 。 基底クラス( private base class) ▼ 継 承 4 際 公開 宣言 関数) 。 理由 、基底 単純 。 。 もしも、派生クラス Derived のメンバ関数やフレンド関数から、基底クラス Base の非公開メ ンバ pri を自由にアクセスできるとしたらどうなるでしょう。クラスの派生を行うだけで、基底 クラスの非公開部を扱えることになってしまいます。これでは情報隠蔽どころではありません。 なお、アクセスできないとはいえ、基底クラスから継承したメンバが消滅したわけではありま せん(派生クラスの中に部分として存在しているのにアクセスできないというだけです) 。 149 chap04/Private.cpp List 4-9 // private派生とメンバのアクセス性(注:エラーとなる行はコメントアウト) #include "Super.h" class Sub : private Super { void f() { // pri = 1; // クラス内部でもアクセスできない pro = 1; pub = 1; } }; ㆒ ■ 派生と継承 4-1 int main() { Sub x; // // // } x.pri = 1; x.pro = 1; x.pub = 1; 、基底 扱 限定公開 、派生 網 ㆒ // クラス外部からアクセスできない // クラス外部からアクセスできない // クラス外部からアクセスできない 利用者 部 派生 Sub 内部( 、基底 ㆓ 公開 基底 Super 、 重要 Super 公開 理由 確認 、以下 分 。 。 関数: 場合 Sub 関数 f 本体) 。 pri 、派生 非公開 内 図 関数 全 図 、派生 対 非公開 ㆓ ■ 利用者 示 対 非公開 。 。 限定公開メンバは、外部に対しては存在を隠すが、自分の子供である派生クラス に対しては存在を隠さない。 class Sub : private Super { //... }; アクセス不可 private 非公開 private 非公開 private 限定公開 protected 限定公開 protected 利用者に非公開 公開 public 公開 public 利用者に公開 クラス Super Fig.4-5 private 派生とメンバのアクセス性 クラス Sub 150 protected 派生 Super protected 派生 行 例 示 protected 派生 、派生 、List 4-10 基底 Sub 、限定公開基 Super 。 底クラス( protected base class) chap04/Protected.cpp List 4-10 // protected派生とメンバのアクセス性(注:エラーとなる行はコメントアウト) #include "Super.h" class Sub : protected Super { void f() { // pri = 1; // クラス内部でもアクセスできない pro = 1; pub = 1; } }; ㆒ ■ int main() { Sub x; // // // } x.pri = 1; x.pro = 1; x.pub = 1; // クラス外部からアクセスできない // クラス外部からアクセスできない // クラス外部からアクセスできない protected 派生 派生 性 関数 点 、private 派生 、基底 扱 利用者 ▼ 継 承 4 。 関数 同様 外部 Fig.4-6 基底 非公開 公開 、public 派生 対 示 。 。 限定公開 点 ㆓ ■ 、派生 異 公開 (当然、 中 限定公開 、派生 ) 。 限定公開メンバは、Sub から派生したクラス(Super の孫に相当するクラス)の内部ではアク セスできますが、その外部からはアクセスできなくなります。 class Sub : protected Super { //... }; アクセス不可 private 非公開 private 非公開 private 限定公開 protected 限定公開 protected 利用者に非公開 公開 public 公開 public 利用者に公開 クラス Super Fig.4-6 protected 派生とメンバのアクセス性 クラス Sub 151 public 派生 public 派生 行 例 示 Super public 派生 、派生 、List 4-11 基底 Sub 。 、公開基底クラ Super 。 ス( public base class) chap04/Public.cpp List 4-11 // public派生とメンバのアクセス性(注:エラーとなる行はコメントアウト) 派生と継承 4-1 #include "Super.h" class Sub : public Super { void f() { // pri = 1; // クラス内部からのアクセス不可 pro = 1; pub = 1; } }; ㆒ ■ int main() { Sub x; // // } x.pri = 1; x.pro = 1; x.pub = 1; // クラス外部からのアクセス不可 // クラス外部からのアクセス不可 // 公開属性が維持される public 派生 他 派生 性 同様 、派生 受 限定公開 、基底 、派生 公開 (限定公開 Fig.4-7 。 関数 、基底 公開 公開 非公開 。 、派生 利用者 叅 ■ 関数 不可能 基底 示 ㆓ ■ 限定公開 中 、派生 公開 (叅) 。 、基底 ) class Sub : public Super { //... }; 性 、派生 扱 扱 非公開以外 維持 。 アクセス不可 private 非公開 private 非公開 private 限定公開 protected 限定公開 protected 利用者に非公開 公開 public 公開 public 利用者に公開 クラス Super Fig.4-7 public 派生とメンバのアクセス性 クラス Sub 152 三つの派生のまとめ 3 種類 派生(private 派生、protected 派生、public 派生) 共通 、以下 ▪ 派生 原理 規則性 “○○部 所属 。 行 、基底 非公開 派生 。 継 承 4 ▪“○○派生 行 、基底 公開 、派生 。 ▪限定公開 、外部 公開 公開 、自分 子供(直接派生 ) 。 基底クラス部分オブジェクトとコンストラクタ初期化子 Fig.4-1(p.145) 考 派生 示 List 4-12 両 対 、実際 。 実現 、 、 追加 派生 。 public 派生 、 、 。 コンストラクタ初期化子 派生 、基底 資産 例外 重要 、必 継承 覚 原則 、 必要 。 クラスの派生において、コンストラクタとデストラクタは継承されない。 、派生 、 、 作 直 必要 。 Column 4-2 基底クラス非公開部のアクセス 派生クラスを基底クラスのフレンドであると明示的に宣言すれば、基底クラスの非公開メンバに もアクセスできるようになります。 class Super { friend class Sub; }; // Sub君は、僕のお友達ですよ!! // … class Sub : Super { // クラスSuperの非公開部を自由にアクセスできる }; クラス Sub は、クラス Super から派生したクラスでもあり、フレンドクラスでもあるということ になります。 ※ ただし、よほど特別な事情でもない限り、このような宣言を行うべきではありません。 153 chap04/BaseDerived.h List 4-12 // 基底クラスと派生クラス #ifndef ___Member #define ___Member #include <iostream> //--- 基底クラス ---// class Base { int a; int b; 派生と継承 4-1 public: Base(int aa, int bb) : a(aa), b(bb) { } void func() const { std::cout << "a = " << a << '\n'; std::cout << "b = " << b << '\n'; } }; 直接基底クラスのコンストラクタを呼び出す //--- 派生クラス ---// class Derived : public Base { int x; public: Derived(int aa, int bb, int xx) : Base(aa, bb), x(xx) { } void method() const { func(); std::cout << "x = " << x << '\n'; } }; #endif 注目 Derived 初期化子 x(xx) 中 一 働 初期化子 。点線部 、 青網部 x 、以下 値 xx 示 形式 初期化子 初期化 。 。 基底クラス名 ( 仮引数宣言節 ) 呼出 、直接基底 化子(constructor initializer) Base 、 呼 指示 。本 、コンストラクタ初期 初期化子 a b 初期化 、親 委 。 基底クラスから継承したデータメンバの初期化は、コンストラクタ初期化子に 重要 ▼ よって、直接基底クラスのコンストラクタに委ねるのが原則である。 クラス Derived のコンストラクタを以下のように定義することはできません。 Derived(int aa, int bb, int xx) : a(aa), b(bb), x(xx) { } // エラー その理由は単純です。基底クラス Base で非公開であるデータメンバ a と b に対するアクセスが、 メンバクラス Derived 内では許可されないからです。 4 4 4 4 また、メンバ初期化子で初期化できるのは、直接基底クラスのみです。間接基底クラス(親ク ラスでなく、お爺さん以上のクラス)の初期化を指定することはできません。 154 基底クラス部分オブジェクト 前 List 4-13 定義 派生 示 、 実際 Derived 一例 利用 作 。 chap04/BaseDerivedTest1.cpp List 4-13 // 派生の一例 継 承 4 実行結果 #include <iostream> #include "BaseDerived.h" dv.func() a = 1 b = 2 dv.method() a = 1 b = 2 x = 3 using namespace std; int main() { Derived dv(1, 2, 3); } cout << "dv.func()\n"; cout << "dv.method()\n"; main 関数 、 、 初期化 // Baseから継承したメンバ関数 // Derivedに所属するメンバ関数 b dv 継承 存在 様子 a dv.func(); dv.method(); Derived 型 Base x 学習 。 a, b 、 示 1, 2, 3 、Fig.4-8 初期化 。 定義 。 、 Derived 初期化 追加 。 、 Derived 委 Base 、前 。 クラス Derived のオブジェクト dv の宣言 Derived dv(1, 2, 3); 派生クラス Derived のコンストラクタ Derived::Derived(int aa, int bb, int xx) : Base(aa, bb), x(xx) { } 基底クラス Base のコンストラクタ 1 2 Base::Base(int aa, int bb) : a(aa), b(bb) { } dv 1 基底クラス部分オブジェクト a 2 b 3 1 2 3 x Fig.4-8 コンストラクタの働きと基底クラス部分オブジェクト 155 、図 示 、派生 ( a 型 中 構成 b ジェクト(base class sub-object) 呼 含 基底 部分) 型 、基底クラス部分オブ 。 派生クラス型のオブジェクトの中に含まれている、基底クラス型のオブジェクト 重要 は、基底クラス部分オブジェクトと呼ばれる。 呼 出 、 、二 関数 func method 。 関数 func 右 、基底 定義 Derived 型 値 対 dv 正 Base 内 func 派生 派生 a 、以下 public 派生 、 公開 継承 a, b 内部 dv 。 Base 公開 非公開 Base 示 、基底 Derived ▪基底 呼出 、 表示 ▪ void Base::func() const { std::cout << "a = " << a << '\n'; std::cout << "b = " << b << '\n'; } 。 dv.func() b 対 dv 派生と継承 、main 関数 4-1 、 基底 関数 func 、 。 Derived 部分 ( )含 。 一 関数 method Derived 内 右 関数 関数 func 定義 黒網部 呼 、 出 呼出 関数呼出 ▪ 。 、左 内 、以下 Base 継承 他 呼 出 関数 b 値 示 、 呼 式 形式 、呼出 黒網部 表示 ( 、基底 ) 。 。 関数 func 関数 Derived 、func 実行結果内 a 実行結果 void Derived::method() const { func(); std::cout << "x = " << x << '\n'; } 。 Base 表示 部分 、派生 出 関数名 () 形式 Derived 中 、 。 注意 。 名 . 関数名 () 。 重要 基底クラスから継承したメンバ関数は、派生クラス型のメンバ関数およびフレン ドの中では、あたかも派生クラス型のメンバ関数であるかのように利用できる。 156 スライシング 引 続 、 Base 利用 Derived 見 考 。List 4-14 。 chap04/BaseDerivedTest2.cpp List 4-14 // スライシング 実行結果 #include <iostream> #include "BaseDerived.h" bsの初期状態 a = 99 b = 99 dvを代入した後 a = 1 b = 2 using namespace std; int main() { Derived dv(1, 2, 3); Base bs(99, 99); cout << "bsの初期状態\n"; bs.func(); bs = dv; cout << "dvを代入した後\n"; // OK:スライシング bs.func(); ㆒ ■ // コンパイルエラー ㆓ ■ dv = bs; // } main 関数 、前 定義 同様、 、 一方、 a, b, x Base 型 a 。 値 b 1, 2, 3 bs 対 表示 99 Derived 型 初期化 、 bs 初期化 dv 。 a b 関数 func 関数 呼 両方 出 99 、二 。 * 、㆒ 型 代入 見 。左側 、左右 Fig.4-9 ㆒ 代入 型 。派生 a 異 値 、1 表現 基底 型 。 型 b 一般的 bs 、右側 代入 dv 内 基底 2 更新 様子 派生 dv 示 、 部分 。 、次 。 基底クラスオブジェクトに対して、派生クラスオブジェクトを代入すると、派生 重要 クラス内の 基底クラス部分オブジェクト の部分がコピーされる。 代入 、 部分、 切 捨 dv 内 基底 、 失 、スライシング(slicing) ▼ 継 承 4 Derived 特有 。 部分 情報( 、代入 代入 場合 、 一部 情報 、 x 値) 欠 。 スライシングは、チーズなどの食材を薄切りにすることなどを表す動詞 slice に由来します。 157 ㆒ bs = dv; ■ 基底クラス型 派生クラス型 bs スライシング dv 1 99 a 99 b 1 派生クラス型オブジェクト dv 内の、基底クラス部分 オブジェクトが代入される。 派生クラス特有の x の値は 切り捨てられる。 a 2 b 3 x 4-1 派生と継承 2 ㆓ dv = bs; ■ 基底クラス型 派生クラス型 bs コンパイルエラー dv 1 1 a 2 2 b 1 代入はできない。 a 2 b 3 x Fig.4-9 基底クラスと派生クラスのオブジェクト間の代入 、逆向 ▼ 代入 代入、 不可能 、派生 。 型 基底 、㆓ 型 。 ここに示すプログラムは、コンパイルエラーの発生を抑止するために、// を付けてコメント アウトしています。 派生クラスオブジェクトに対して、基底クラスオブジェクトを代入することはで 重要 きない。 規則 ▼ x 代入 理由 値 単純 決定不能 。 、 代入 。 まさか、データメンバに不定値を代入するわけにはいかないですね。 指示 、 158 継承とデフォルトコンストラクタ 基底 派生 間接的 、 、List 4-15 形 継承 示 学習 p.152 。 。 検討 。 chap04/constructor.cpp List 4-15 // 基底クラスと派生クラスのコンストラクタ 実行結果 #include <iostream> Base::xを99で初期化。 d.get_x() = 99 using namespace std; //===== 基底クラス =====// class Base { int x; public: //--- コンストラクタ ---// Base() : x(99) { cout << "Base::xを99で初期化。\n"; } }; //--- xのゲッタ ---// int get_x() const { return x; } //===== 派生クラス =====// class Derived : public Base { // コンストラクタを含め何も定義しない }; int main() { Derived d; } cout << "d.get_x() = " << d.get_x() << '\n'; 唯一 Base 初期化 。 Derived 、 、int 型 関数 get_x 、 派生 Base 、 1個 自動的 x 。 。 Ⓐ Ⓐ Derived::Derived() { } ■ 。 x x 、下記 ▼ 99 定義 定義 。 // コンパイラによって定義されるコンストラクタ? コンストラクタを定義しないクラスに対しては、本体が空であるコンストラクタが、コンパイ ラによって自動的に定義されることは、 『入門編』で学習しました。 main 関数 呼 、 Derived 型 出 本体 含 ▼ 継 承 4 継承 、 空 宣言 d x ) 。 初期値 、 x 99 初期化 。上記 不定値 d 内 基底 ( 部分 。 実行結果(メンバ関数 d.get_x の呼出しによって 99 が返却されること)で確認できます。 159 実 、 自動的 )以下 定義 、 (概念上 。 ʙ Derived::Derived() : Base() { } 網 部 // コンパイラによって定義されるコンストラクタ 、直接基底 呼出 Base 。Base 自動的 呼 出 、 x 初期化 99 4-1 直接基底 4 4 4 重要 4 継承 4 暗黙裏 呼 派生と継承 。 、基底 出 分 。 コンパイラによって定義されるデフォルトコンストラクタは、直接基底クラスの デフォルトコンストラクタを呼び出す。 以下 Base 、 Base 定義 置 。 、 。 Derived ase::Base(int xx) : x(xx) { cout << "Base::xを" << x << "で初期化。\n"; } ㆒ B■ ■ ʙ Derived 呼 出 処理』 網かけ部 不可能 、 『 Base 。 * 、 引数 、 引数 与 呼 受 取 、 出 以下 。 定義 、 、 回避 Base 。 ㆓ Base::Base(int xx = 99) : x(xx) { cout << "Base::xを" << x << "で初期化。\n"; } ■ 検討 内容 、以下 示 注意点 得 。 コンストラクタを定義しないクラスは、そのクラスの直接基底クラスが 引数を 重要 与えることなく呼び出せるデフォルトコンストラクタ をもっていなければ、コン ▼ パイルエラーとなる。 デフォルト(default)には、 『既定(値) 』 、 『省略時(指定がない場合)に採用される選択(値) 』 、 などの意味があります。 演習 4-1 List 4-15 のクラス Base のコンストラクタを上記の㆓に置きかえたプログラムを作成して、本ペー ジで解説されている内容を確認せよ。 160 派生クラスオブジェクトの初期化 実行 順序 際 起動 初期化 検証 List 4-16 継 承 4 、 本体 初期化子 行 。 、 。 chap04/initialize.cpp List 4-16 // 基底クラスとメンバの初期化を確認するクラス群 #include <iostream> using namespace std; //===== クラスDerivedの基底クラス =====// class Base { int x; public: Base(int a = 0) : x(a) { cout << "Base::xを" << x << "で初期化。\n"; } }; //===== クラスDerivedにメンバとして含まれるクラス =====// class Memb { int x; public: Memb(int a = 0) : x(a) { cout << "Memb::xを" << x << "で初期化。\n"; } }; //===== クラスDerivedはクラスBaseからpublic派生 =====// class Derived : public Base { int y; Memb m1; Memb m2; void say() { y = 0; cout << "Derived::yを" << y << "で初期化。\n"; } public: Derived() { say(); } Derived(int a, int b, int c) : m2(a), m1(b), Base(c) { say(); } }; 実行結果 int main() { Derived d1; cout << '\n'; Derived d2(1, 2, 3); } 本 単純 Base Base::xを0で初期化。 Memb::xを0で初期化。 Memb::xを0で初期化。 Derived::yを0で初期化。 Base::xを3で初期化。 Memb::xを2で初期化。 Memb::xを1で初期化。 Derived::yを0で初期化。 構造 三 、実質的 Memb 構造 、int 型 。両 x 受 同 。 取 値 初期化 x public 派生 Base Memb 型 0 初期化 旨 。 、int 型 、 旨 表示 、仮引数 a y 加 、 呼 出 、 。 m2 二 y 、 Derived m1 多重定義 行 、 表示 関数 say 。 161 main 関数 、 Derived 初期化 過程 詳 ▪Derived::Derived() 基底 呼 定義 。各 。 初期化 二 Base Memb 型 、実行結果 自動的 分 。 出 (各 実引数 値0 渡 4-1 ) 。 既に学習したように、もしクラス Base と Memb にデフォルトコンストラクタがなければ、この コンストラクタはコンパイルエラーとなります。 ▪Derived::Derived(int a, int b, int c) 初期化子 基底 。実行結果 ▪基底 順序 呼 、 示 初期化 ▪値 1 指定 初期化 初期化 指定 、次 基底 宣言 並 行 。 一致 m2, m1, Base 指定 、基底 示 順 初期化 実行 先 m1 順序 基底 本体 初期化 。 、値 2 初期化作業 m2, m1 。 先 m2 出 初期化子 、以下 先 、 m2(a), m1(b), Base(c) Base 、初期化 初期化 d2 行 初期化 。必 部分 。 覚 。 初期化 。 。 。 ▼ の 宣言された順 は、コンストラクタ初期化子の並びの順ではなく、クラス定義における データメンバ自体の宣言の順序です。このことは、 『入門編』の p.411 で学習済みです。 重要 コンストラクタに指定するメンバ初期化子の並びの順序は、先頭を基底クラスコ ンストラクタ初期化子とし、その後に、データメンバの宣言と同じ順序でデータメ ンバ初期化子を並べたものとすると、紛らわしさがなくなる。 後始末 、 、 逆 順序 解体 行 。 演習 4-2 デストラクタの起動の順序を確認できるように、List 4-16 のプログラムを書きかえたプログラム を作成せよ。 派生と継承 、呼 d2 見 d1 出 ▼ d1 162 コピーコンストラクタとデストラクタと代入演算子 派生 学習 、 。 間接的 、 以外 特殊 、代入演算子 関数 継承 、 、 確認 。 継 承 chap04/Array.cpp List 4-17 // 基底クラスと派生クラスの代入演算子とデストラクタ #include <iostream> 実行結果 using namespace std; //===== 超簡易配列クラス =====// class Array { static const int num = 5; int *p; // 要素数(固定) public: //--- デフォルトコンストラクタ ---// Array() : p(new int[num]) { cout << "領域確保\n"; } //--- コピーコンストラクタ ---// Array(const Array& x) : p(new int[x.num]) { for (int i = 0; i < num; i++) p[i] = x.p[i]; cout << "コピー初期化\n"; } 領域確保 コピー初期化 領域確保 配列a1:15 15 15 15 15 配列a2:15 15 15 15 15 配列a3:15 15 15 15 15 領域解放 領域解放 領域解放 // xの全要素をコピー //--- デストラクタ ---// ~Array() { delete[] p; cout << "領域解放\n"; } //--- 代入演算子 ---// Array& operator=(const Array& x) { for (int i = 0; i < num; i++) p[i] = x.p[i]; return *this; } //--- 全要素に値vを代入 ---// void set(int v) { for (int i = 0; i < num; i++) p[i] = v; } }; //--- 全要素の値を表示 ---// void print() const { for (int i = 0; i < num; i++) cout << p[i] << ' '; } //===== 超簡易配列クラス(派生クラス)=====// class ArrayX : public Array { // コンストラクタを含め何も定義しない }; int main() { ArrayX a1; a1.set(15); } 既 。 、List 4-17 4 形 // a1の全要素に15を代入 ArrayX a2(a1); // a1で初期化 ArrayX a3; a3 = a1; // a1の全要素をa3にコピー cout << "配列a1:"; cout << "配列a2:"; cout << "配列a3:"; a1.print(); a2.print(); a3.print(); cout << '\n'; cout << '\n'; cout << '\n'; 163 Array 要素数 、1-3 節 固定 5 学習 配列 簡易化 IntArray 。配列 。 ▼ 定義されているメンバ関数の働きは、以下のとおりです。 ▪デフォルトコンストラクタは、配列用の領域を確保します。 ▪コピーコンストラクタは、配列用の領域を確保して、仮引数 x に受け取ったコピー元配列の全 要素をコピーします。 ▪デストラクタは、配列用の領域を解放します。 ▪メンバ関数 set は、全要素に同一値 v を代入します。 ▪メンバ関数 print は、全要素の値を表示します。 派生 働 確認 派生 実験用 。 ArrayX 中 、 public 、 Array 関数 一切定義 。 main 関数 、ArrayX 型 a1, a2, a3 、 働 、 、 基底 Array 定義 。 、代入演算子 同 働 、期待 、実験結果 確認 。 派生クラス内で、特殊メンバ関数(コピーコンストラクタ、デストラクタ、代入 重要 演算子)が定義されなければ、基底クラスのものと実質的に同一の働きをする関数 が自動的に定義される。 、 public 自動的 定義 。代入演算子 特殊 、具体的 関数 、 、以下 inline 。 派生クラス X 内で代入演算子が定義されなければ、以下の形式の代入演算子が自 重要 動的に定義される。 ▼ X& X::operator=(const X&); 自動的に定義される代入演算子の形式が上記のようになるのは、以下の二つの条件のいずれも が満たされる場合です(基底クラスの名前が B であるとします) 。 ▪直接基底クラスが、const B&,const volatile B& あるいは B を仮引数の型とするコピー代入 演算子をもつ。 ▪そのクラスのすべてのクラス型 M(あるいはその配列型)の非静的データメンバについて、そ れらのクラス型が、const M&,const volatile M& あるいは M を仮引数の型とするコピー代入 演算子をもつ。 そうでない場合、暗黙に定義される代入演算子は、次の形式となります。 X& X::operator=(X&); 派生と継承 4-1 ▪代入演算子は、x の全要素を自分自身の配列にコピーします。 164 継承と差分プログラミング 派生 継 承 基本的 会員 Member 員 示 ▼ 4 関 学習 派生 。 部 一通 終了 行 。優待会員 変更 List 4-18 VipMember 。 、 部 実現 List 4-19 、一般 優待会 。 List 4-4(p.142)と List 4-5(p.143)は、試作版であったことから、クラス名を便宜的に VipMember0 としていました。今回は、クラス名を VipMember にしています。 Member1/VipMember.h List 4-18 // 優待会員クラス(第1版:ヘッダ部) #ifndef ___VipMember #define ___VipMember #include <string> #include "Member.h" 基底クラスの定義を取り込む //===== 優待会員クラス(第1版:ヘッダ部)=====// class VipMember : public Member { std::string privilege; // 特典 public: //--- コンストラクタ ---// VipMember(const std::string& name, int no, double w, const std::string& prv); //--- 特典取得(privilegeのゲッタ)---// std::string get_privilege() const { return privilege; } }; //--- 特典設定(privilegeのセッタ)---// void set_privilege(const std::string& prv) { privilege = (prv != "") ? prv : "未登録"; } #endif Member1/VipMember.cpp List 4-19 // 優待会員クラス(第1版:ソース部) #include <iostream> #include "VipMember.h" using namespace std; //--- コンストラクタ ---// VipMember::VipMember(const string& name, int no, double w, const string& prv) : Member(name, no, w) { set_privilege(prv); // 特典を設定 } 以下 示 三 ▪ 、基底 Member 継承 。 full_name, number, weight 、基底 不可能 非公開 。 、派生 VipMember 関数 165 、 VipMember Member 公開 、派生 、一般会員 利用 、基底 公開 VipMember 継承 Member 状態 ▪ “public 派生 Member 。 、 、以下 VipMember 外部 。 関数 name, no, get_weight, set_weight 、以下 ▪ 新 定義 4-1 。 派生と継承 、優待会員 privilege ▪ 関数 get_privilege, set_privilege ▪ VipMember 、網 体重 初期化 部 直接基底 初期化子 、名前、会員番号、 委 Member 。 * 試作版・優待会員 第1版 優待会員 利用 VipMember0 用 書 例(List 4-6:p.143) List 4-20 、 。 Member1/VipMemberTest.cpp List 4-20 // 優待会員クラス(第1版)の利用例 #include <iostream> #include "VipMember.h" 実行結果 No.17:峰屋龍次 (73.9kg)特典=会費全額免除 using namespace std; int main() { VipMember mineya("峰屋龍次", 17, 89.2, "会費全額免除"); double weight = mineya.get_weight(); mineya.set_weight(weight - 15.3); cout << "No." << mineya.no() << ":" << mineya.name() << "(" << mineya.get_weight() << "kg)" << " 特典=" << mineya.get_privilege() << '\n'; 基底 継承 公開 関数 no, name, get_weight, set_weight VipMember 型 適用 mineya 確認 、派生 。 * 継承 一 、異 部分 追加 部分 差分プログラミング(incremental programming) 、 ▼ } // 峰屋君の体重 // 峰屋君の体重を更新(15.3kg減量) 開発 効率 保守性 向上 作成 行 図 開発 。 抑制 継承 行 。 継承の本当のメリットは差分プログラミングではありません。次節で学習する is-A の関係の 実現や、それを応用した(次章以降で学習する)多相性( )の実現です。
© Copyright 2024 ExpyDoc