4-1『派生と継承』 - BohYoh.com

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 の関係の
実現や、それを応用した(次章以降で学習する)多相性(
)の実現です。