新規記事の投稿を行うことで、非表示にすることが可能です。
2017年12月16日
《その181》 メンバ関数の隠蔽(派生クラス内で同名の関数を定義)
基底クラスのメンバ関数の隠蔽
派生クラス内で、基底クラスのメンバ関数と同名の関数を定義すると、基底クラスのメンバ関数は隠蔽されます。
次のプログラムでは、基底クラス Aaa がメンバ関数 print を持っていて、派生クラス Bbb も同名のメンバ関数 print を持っています。このようなときは、基底クラスのほうのメンバ関数が隠蔽されます。
Aaa のメンバ関数は print();
Bbb のメンバ関数は print(int x);
で、仮引数が異なりますが、仮引数とは関係なく関数名が同じであれば隠蔽されます。
基底クラス Aaa のメンバ関数を呼び出す場合には、有効範囲解決演算子「 :: 」を用いて Aaa::print() のようにします。
// ------------------------------------
#include <string>
#include <iostream>
class Aaa {
int a;
public:
Aaa(int x = 10) : a(x) { }
void print() { std::cout << "a = " << a << '\n'; }
// クラス Aaa のメンバ関数
};
class Bbb : public Aaa{
int b;
public:
Bbb(int x = 20, int y = 200) : Aaa(x), b(y) { }
void print(int x) {
std::cout << "整数 " << x
<< " を受け取りました。\n";
std::cout << "b = " << b << '\n'; }
// クラス Bbb のメンバ関数
// 基底クラス Aaa のメンバ関数と同名なので、
// 基底クラスの関数 print は隠蔽されます。
};
int main() {
Aaa aaa; // Aaa型オブジェクトの aaa を生成。
Bbb bbb; // Bbb型オブジェクトの bbb を生成。
aaa.print();
std::cout << '\n';
bbb.print(66);
// Bbb型の関数 print が呼び出されます。
// (Aaa型の関数 print は隠蔽されているため)
std::cout << '\n';
bbb.Aaa::print();
// 有効範囲解決演算子「 :: 」を用いて Aaa::print
// とすれば、基底クラス Aaa の関数 print
// にアクセスすることができます。
}
// ------------------------------------
《その180》 継承と静的メンバ & p.174演習4-4
継承と静的メンバ
静的メンバを含むクラスから派生した子クラスは、親クラスである基底クラスの静的メンバを、そのまま静的メンバとして継承します。
次のプログラムで、その様子が確認できます。
親クラス型のオブジェクト,子クラス型のオブジェクト,孫クラス型のオブジェクトが、それぞれのオブジェクト型とは無関係に、作成された順にマイナンバー my_num を割り当てられています。
// ------------------------------------
#include <iostream>
class Num {
static int num;
// Num型のオブジェクトを作成した個数を記録する
// ための静的データメンバです。
int my_num;
// 各オブジェクト自身が、何番目に作られたもので
// あるか、オブジェクト作成時にその
// 値で初期化されます。
public:
Num() : my_num(++num) { }
// コンストラクタ
// オブジェクトを作成する毎に、num の値をイ
// ンクリメントし、
// その値で、各オブジェクトの my_num を初期
// 化します。
void my_n() const {
std::cout << my_num << '\n';
}
// my_num のゲッタです。
};
/* public派生した子クラス */
class Aaa : public Num {
int a;
public:
Aaa(int x = 1000) : a(x) { }
int get_a() { return a; }
};
/* public派生した孫クラス */
class Bbb : public Aaa {
int b;
public:
Bbb(int x = 2000) : b(x) { }
int get_b() { return b; }
};
int Num::num = 0;
int main()
{
Num n1; // 1個目の Num型オブジェクト
n1.my_n(); // 1
Num n2; // 2個目の Num型オブジェクト
n2.my_n(); // 2
Aaa a1; // 1個目の Aaa型オブジェクト
a1.my_n(); // 3
Aaa a2; // 2個目の Aaa型オブジェクト
a2.my_n(); // 4
Bbb b1; // 1個目の Bbb型オブジェクト
b1.my_n(); // 5
Num n3; // 3個目の Num型オブジェクト
n3.my_n(); // 6
}
// ------------------------------------
新版明解C++中級編 p.174 演習4-4
次のクラス ResigningMember へのポインタからクラス Member へのポインタへのアップキャストが行えるかどうか、プログラムを作成して確認せよ。
// ------------------------------------
/* 一般会員クラス Member */
class Member {
std::string full_name; // 氏名
int number; // 会員番号
double weight; // 体重
public:
Member(const std::string& name, int no, double w)
: full_name(name), number(no) {
set_weight(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;
}
};
/* 退会済み会員クラス ResigningMember */
class ResigningMember : private Member {
public:
ResigningMember(
const std::string& name,
int number,
double w
) : Member(name, number, w) { }
// weightのゲッタを再定義
double get_weight() {
std::cout
<< "退会した会員の体重の"
"取得はできません。\n";
return 0;
}
using Member::no; // using宣言
};
// ------------------------------------
◆以下が解答のプログラムです。
// p174_演習4-4
#include <string>
#include <iostream>
/* 一般会員クラス MeMber */
class Member {
std::string full_name; // 氏名
int number; // 会員番号
double weight; // 体重
public:
Member(const std::string& name, int no, double w)
: full_name(name), number(no) {
set_weight(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;
}
};
/* 退会済み会員クラス ResigningMember */
class ResigningMember : private Member {
public:
ResigningMember(
const std::string& name,
int number,
double w
) : Member(name, number, w) { }
// weightのゲッタを再定義
double get_weight() { // ★2.
std::cout
<< "退会した会員の体重の"
"取得はできません。\n";
return 0;
}
using Member::no; // using宣言★1.
};
int main()
{
ResigningMember oosugi("大杉毬藻", 26, 50.7);
//■// Member* ptr1 = &oosugi; // ← エラー
// [コンパイラからのメッセージ]
// (1) アクセスできない基底クラス "Member"
// への変換は許可されていません。
// (2) '型キャスト': 'ResigningMember *'
// から'Member *'の変換は存在し
// ますが、アクセスできません。
ResigningMember* ptr2 = &oosugi; //▲//
std::cout << "会員番号 : "
<< ptr2->no() << '\n';
// ★1.印のusing宣言により、アクセス権あり。
// cout << "氏名 : " << oosugi.name()
// << '\n'; // ← エラー
std::cout << "体重 : "
<< ptr2->get_weight() << '\n';
// ★2.印の再定義により、元々の get_weight
// は隠蔽され、再定義され
// た get_weight が呼ばれ
// る。
// oosugi.set_weight(45.3); // ← エラー
/*
上の■印の結果によれば、クラス ResigningMember
へのポインタからクラス Member へのポインタへの
アップキャストは、行えない、と言えます。
*/
// ************ 以上が解答です ************
// ************ 以下は付け足し ************
/*
上のプログラムの、■印のようなやり方で、直接的
に、Resigning型オブジェクトである oosugi へのポイ
ンタを、Member*型の変数 ptr1 に代入しようとする
アップキャストは、エラーになります。
そこで、一旦、上のプログラムの▲印にもあるような、
ResigningMember* ptr2 = &oosugi;
という、問題のない方法で ResigningMember*型の
ポインタ ptr2 を作成した上で、そのポインタを次のよう
にアップキャストしてみます。
Member* ptr3 = (Member*)ptr2;
このようにして作成したポインタ ptr3 を使用する
と、次のようなアクセスが可能になります。
すなわち、
派生クラスオブジェクト oosugi の中に
基底クラス部分オブジェクトとして存在する基底クラスオブ
ジェクトMember のメンバ関数 get_weight() に、アク
セスできるようになります。
つまり、
std::cout << ptr3->get_weight() << '\n';
が、可能になります。
*/
std::cout << "\n以下は、付け足しです。\n";
Member* ptr3 = (Member*)ptr2;
std::cout << ptr3->get_weight() << '\n';
}