新規記事の投稿を行うことで、非表示にすることが可能です。
2017年12月19日
《その189》 仮想関数テーブル
仮想関数テーブル
次の (1), (2) のプログラムの相違点は、基底クラス Aaa のメンバ関数 f が仮想関数であるかないかだけです。
具体的には、Aaa のメンバ関数 f() の前に "virtual" が付いているかいないかの違いです。
(1) には "virtual" があるので、Aaa&型参照 ref の参照先 obj_b の型は 動的な型 Bbb であり、関数 f() を呼び出した場合には Bbb::f() が呼ばれます。
多相的クラスには、最終オーバライダ(派生により最終的にオーバライドされた関数)へのポインタを記録した仮想関数テーブルが用意されており、プログラムは、そのテーブルを参照して Bbb::f() を呼び出します。
多相的クラスのオブジェクトは、そのクラス用の仮想関数テーブルへのポインタを持っていますから、各オブジェクトは、必要な時に、仮想関数テーブルを参照できるわけです。
仮想関数を利用する場合には、このように、各オブジェクトが仮想関数テーブルへのポインタ用の領域を持ち、さらに、仮想関数テーブルも記憶域を占有しますから、メモリの消費がその分だけ増加します。
(2) には "virtual" がないので、Aaa&型参照 ref の参照先 obj_b の型は 静的な型 Aaa であり、関数 f() を呼び出した場合には Aaa::f() が呼ばれます。
プログラム(1) 仮想関数有りの場合
// ------------------------------------
#include <typeinfo>
#include <iostream>
using namespace std;
class Aaa {
public:
virtual void f() { cout << "Aaa::f()" << '\n'; }
};
class Bbb : public Aaa {
public:
void f() { cout << "Bbb::f()" << '\n'; }
};
int main() {
Bbb obj_b;
Aaa& ref = obj_b;
cout << typeid(obj_b).name() << '\n';
// obj_b は常に class Bbb型です。
cout << typeid(ref).name() << '\n';
// クラス Bbb は多相的クラス(仮想関数を有するクラス)なので、
// Bbb型オブジェクト obj_b への
// Aaa&型の参照 ref は、動的な型
// Bbb です。
obj_b.f();
// Bbb::f() が呼び出されます。
ref.f();
// f() が仮想関数なので、Bbb::f() が呼び出されます。
}
// ------------------------------------
プログラム(2) 仮想関数無しの場合
// ------------------------------------
#include <typeinfo>
#include <iostream>
using namespace std;
class Aaa {
public:
void f() { cout << "Aaa::f()" << '\n'; }
};
class Bbb : public Aaa {
public:
void f() { cout << "Bbb::f()" << '\n'; }
};
int main() {
Bbb obj_b;
Aaa& ref = obj_b;
cout << typeid(obj_b).name() << '\n';
// obj_b は常に class Bbb型です。
cout << typeid(ref).name() << '\n';
// クラス Bbb は多相的クラスではないので、
// Bbb型オブジェクト obj_b への
// Aaa&型の参照 ref は、静的な型
// Aaa です。
obj_b.f();
// Bbb::f() が呼び出されます。
ref.f();
// Aaa::f() が呼び出されます。
}
// ------------------------------------
《その188》 ポリモーフィズム
ポリモーフィズム(多相性)
下記のプログラムには、クラスが3つあります。
Aaaは基底クラス、Bbb,Ccc は Aaa のpublic派生クラスです。
【 基底クラス Aaa 】
仮想メンバ関数 f は、仮引数 n が奇数であるか偶数であるかを判断します。
【 派生クラス Bbb 】
メンバ関数 f は、Aaa の仮想メンバ関数 f をオーバライドしています。
クラス Bbb の関数 f は、仮引数 n が 3 の倍数であるかどうかを判断します。
【 派生クラス Ccc 】
オーバライダ f は、仮引数 n が 10以上か 10未満かを判断します。
※ オーバライドした関数は、オーバライダと呼ばれます。
【 関数 func(Aaa& x, int n) 】
3つの型すべてのオブジェクトへの参照を、仮引数 x で受け取ることができます。
【 main関数 】
次のことをします。
・ユーザから整数 n を受け取る。
・Aaa型のオブジェクト obj_a を生成。
・Bbb型のオブジェクト obj_b を生成。
・Ccc型のオブジェクト obj_c を生成。
・関数 func に obj_a と 整数 n を渡す。
・関数 func に obj_b と 整数 n を渡す。
・関数 func に obj_c と 整数 n を渡す。
3つのクラスオブジェクト obj_a, obj_b, obj_c はそれぞれ型が異なっていますが、基底クラス型が Aaa型という共通点を利用して、同じ扱いができます。
メッセージを受け取った各オブジェクトは、それぞれ自身の動的な型に応じた振舞いをします。
ポリモーフィズム(多相性)
多相的クラス( 当ブログの《185》)オブジェクトは、派生による階層関係を持っています。このような関係にある複数のクラスオブジェクトに対して、次のことが可能になります。
※基底クラスが同一であるので、基底クラス型への参照(あるいはポインタ)を通じて、型の異なる複数のオブジェクトに同じメッセージを送ることができます。
※メッセージを受け取った各オブジェクトは、自身の動的な型に応じた振舞いをします。
このような手法(考え方・方針)をポリモーフィズムと呼んでいます。
// ------------------------------------
#include <iostream>
using namespace std;
class Aaa {
public:
virtual void f(int n) const {
cout << n << " は"
<< ( n % 2 ? "奇数" : "偶数" )
<< "です。\n";
}
};
class Bbb : public Aaa {
public:
void f(int n) const {
cout << n << " は 3 の倍数で"
<< (n % 3 ?
"はありません。" : "す。")
<<'\n';
}
};
class Ccc : public Aaa {
public:
void f(int n) const {
cout << n << " は 10 "
<< (n < 10 ? "未満" : "以上")
<< "です。\n";
}
};
void func(Aaa& x, int n) {
x.f(n);
}
int main() {
int n;
cout << "整数を入力 : "; cin >> n;
Aaa obj_a; Bbb obj_b; Ccc obj_c;
func(obj_a, n);
func(obj_b, n);
func(obj_c, n);
}
// ------------------------------------