新規記事の投稿を行うことで、非表示にすることが可能です。
2017年12月22日
《その194》 type_infoクラス
type_infoクラス
type_infoクラスは、型情報を格納する型であり、<typeinfo>ヘッダによって提供されます。
typeid演算子によってのみ生成される type_infoオブジェクトには、型名文字列へのポインター その他の型情報が格納されています。
( typeid演算子が生成するのは、const std::type_info型の左辺値です。typeid演算子の結果は、その参照になります。)
typeid演算子に型 あるいは オブジェクトを渡すことにより生成される type_infoオブジェクトは、その型 あるいは オブジェクト の実行時型情報 RTTI です。
type_infoクラスの定義は、概略としては、以下のようになっています。
class type_info {
public:
virtual ~type_info();
bool operator==(const type_info&) const;
bool operator!=(const type_info&) const;
const char* name() const;
・・・
その他
・・・
private:
type_info(const type_info&);
type_info& operator=(const type_info&);
};
・ メンバ関数 name は、処理系定義の NTBS を返却します。
( NTBS … Null-Terminated Byte String のことで、末尾がナル文字になっている C言語形式の文字列です。)
・ type_info コピー コンストラクターは private であるため、type_infoクラスのオブジェクトを直接インスタンス化することはできません( ★1.)。
( type_info オブジェクトの生成は、typeid 演算子によってのみ可能です。)
・ 代入演算子「 = 」も private です。したがって、type_infoオブジェクトのコピーや代入を行うことはできません( ★2.)。
// ------------------------------------
#include <typeinfo>
#include <iostream>
int main() {
std::cout << typeid(int).name() << '\n';
// const type_info obj1 = typeid(int); // エラー ★1.
const type_info& ref1 = typeid(double);
std::cout << ref1.name() << '\n';
const type_info& ref2 = typeid(unsigned);
std::cout << ref2.name() << '\n';
const type_info* ptr = &typeid(double);
std::cout << ptr->name() << "\n\n";
// ref1 = ref2; // エラー ★2.
std::cout << std::boolalpha
<< (typeid(int) == typeid(5)) << '\n';
// operator==
std::cout << (typeid(int) != typeid(5)) << '\n';
// operator!=
}
// ------------------------------------
《その193》 typeid演算子
typeid演算子
typeid演算子は、多相的オブジェクトのように、実行時に型が決定するような場合、その実行時型情報 RTTI(Run-Time Type Information)を取得できる演算子です。
多相的クラス型のオブジェクトを typeid演算子のオペランドに与えると、実行時に決定された型を表現する const type_info型クラスオブジェクトへの参照が返されます。
例えば、多相的クラスオブジェクト x が実行時において Bbb型であるような場合、
std::cout << typeid(x).name(); // ★
と記述した場合には、
「 class Bbb 」 と出力されます。
★の name() は、type_infoクラスのメンバ関数です。
typeid(x)
で、typeid演算子のオペランドに x を与えています。すると、実行時に決定された型 Bbb を表現する type_infoクラスオブジェクトへの参照が返されます。
したがって、
std::cout << typeid(x).name();
は、例えば、
const type_info& ref = typeid(x);
std::cout << ref.name() << '\n';
あるいは、
const type_info* ptr = &typeid(x);
std::cout << ptr->name();
と同じ意味です。
// ------------------------------------
#include <iostream>
class Aaa {
virtual void f() const { };
};
class Bbb : public Aaa {
void f() const { };
};
void func(Aaa& x);
int main() {
Bbb obj_b;
Aaa& ref = obj_b;
func(obj_b);
}
void func(Aaa& x) {
std::cout << typeid(x).name() << '\n';
const type_info& ref = typeid(x);
std::cout << ref.name() << '\n';
const type_info* ptr = &typeid(x);
std::cout << ptr->name() << '\n';
}
// ------------------------------------
2017年12月21日
《その192》 仮想関数の利用( 単純な例 ;^ω^A )
自分でちょっと確認したいことがあって作ったプログラムです。仮想関数が、クラスオブジェクトの動的な型に応じて動作する単純なプログラムです。
※ 派生クラスの中の基底クラス部分オブジェクトへのアクセス(name, tool へのアクセス)について、自分がチェックしてみたいことがあって作ったプログラムです。
// ------------------------------------
#include <string>
#include <iostream>
using namespace std;
class Man0 {
string name;
string tool;
public:
Man0(string na, string tl = "ハサミ")
: name(na), tool(tl) { }
string get_name() { return name; }
string get_tool() { return tool; }
void set_tool(string s) { tool = s; }
virtual void hello() { cout << "こんにちは。"; }
void my_name() {
cout << "私は" << name << "です。";
}
virtual void func() {
cout << tool << "を持っています。\n";
}
};
class Man1 : public Man0 {
string tool;
public:
Man1(string na, string tl = "定規")
: Man0(na), tool(tl) { }
void set_tool(string s, string s2) {
Man0::set_tool(s); tool = s2;
}
void hello() { cout << "よろしくね。"; }
void func() {
cout << get_tool() << "と"
<< tool << "を持っているよ。\n";
}
};
void statement(Man0& x) {
x.hello();
x.my_name();
x.func();
}
int main() {
Man0 tanaka("田中");
Man1 nakada("中田");
statement(tanaka);
statement(nakada);
cout << '\n';
tanaka.set_tool("画用紙");
nakada.set_tool("クレヨン", "鉛筆");
statement(tanaka);
statement(nakada);
}
// ------------------------------------
《その191》 仮想関数,仮想デストラクタ,多相的クラス
virtual無し と virtual有りの 短いプログラムを比較して、仮想関数,仮想デストラクタ,多相的クラス の再確認をしてみたいと思います。
◆ virtual無し
// ------------------------------------
#include <iostream>
using namespace std;
class Aaa {
public:
Aaa() { cout << "constructor Aaa\n"; }
~Aaa() { cout << "destructor Aaa\n"; }
void f() const { cout << "Aaa\n"; }
};
class Bbb : public Aaa{
public:
int b = 20;
Bbb() { cout << "constructor Bbb\n"; }
~Bbb() { cout << "destructor Bbb\n"; }
void f() const { cout << "Bbb\n"; }
};
void func(Aaa& x) {
x.f();
}
int main() {
cout << "(1)\n";
Aaa* ptr = new Bbb;
cout << "------\n";
cout << "(2)\n";
delete ptr;
cout << "------\n";
cout << "(3)\n";
Bbb b;
cout << "------\n";
cout << "(4)\n";
func(b);
cout << "------\n";
cout << "(5)\n";
}
// ------------------------------------
(1)
動的に領域を確保して、Bbb型オブジェクトを作成するので、
基底クラスのコンストラクタ Aaa が呼び出され、
→ "constructor Aaa"を表示
次いで、派生クラスのコンストラクタ Bbb が呼び出されます。
→ "constructor Bbb"を表示
(2)
Aaa, Bbb は多相的クラスではないのでポインタ ptr は静的な型 Aaa*。よって、デストラクタ ~Aaa が呼ばれます。
→ "destructor Aaa"を表示
(3)
Bbb型オブジェクトを作成するので、
基底クラスのコンストラクタ Aaa が呼び出され、
→ "constructor Aaa"を表示
次いで、派生クラスのコンストラクタ Bbb が呼び出されます。
→ "constructor Bbb"を表示
(4)
関数 func の仮引数は Aaa&型。f() は仮想関数ではないので、Aaa::f() が呼び出されます。
→ "Aaa"を表示
(5)
プログラムの終了時に オブジェクト b を破棄するので、
デストラクタ ~Bbb が呼び出され、
→ "destructor ~Bbb"を表示
次いで、デストラクタ ~Aaa が呼び出されます。
→ "destructor ~Aaa"を表示
◆ virtual有り
// ------------------------------------
#include <iostream>
using namespace std;
class Aaa {
public:
Aaa() { cout << "constructor Aaa\n"; }
virtual ~Aaa() { cout << "destructor Aaa\n"; }
virtual void f() const { cout << "Aaa\n"; }
};
class Bbb : public Aaa{
public:
int b = 20;
Bbb() { cout << "constructor Bbb\n"; }
~Bbb() { cout << "destructor Bbb\n"; }
void f() const { cout << "Bbb\n"; }
};
void func(Aaa& x) {
x.f();
}
int main() {
cout << "(1)\n";
Aaa* ptr = new Bbb;
cout << "------\n";
cout << "(2)\n";
delete ptr;
cout << "------\n";
cout << "(3)\n";
Bbb b;
cout << "------\n";
cout << "(4)\n";
func(b);
cout << "------\n";
cout << "(5)\n";
}
// ------------------------------------
(1)
動的に領域を確保して、Bbb型オブジェクトを作成するので、
基底クラスのコンストラクタ Aaa が呼び出され、
→ "constructor Aaa"を表示
次いで、派生クラスのコンストラクタ Bbb が呼び出されます。
→ "constructor Bbb"を表示
(2)
Aaa, Bbb は多相的クラスであるからポインタ ptr は動的な型 Bbb*。よって、
デストラクタ ~Bbb が呼ばれ、
→ "destructor ~Bbb"を表示
次いで、デストラクタ ~Aaa が呼ばれます。
→ "destructor ~Aaa"を表示
(3)
Bbb型オブジェクトを作成するので、
基底クラスのコンストラクタ Aaa が呼び出され、
→ "constructor Aaa"を表示
次いで、派生クラスのコンストラクタ Bbb が呼び出されます。
→ "constructor Bbb"を表示
(4)
関数 func の仮引数は Aaa&型ですが、f() が仮想関数なので、Bbb::f() が呼び出されます。
→ "Bbb"を表示
(5)
プログラムの終了時に オブジェクト b を破棄するので、
デストラクタ ~Bbb が呼び出され、
→ "destructor ~Bbb"を表示
次いで、デストラクタ ~Aaa が呼び出されます。
→ "destructor ~Aaa"を表示
2017年12月20日
《その190》 仮想デストラクタ
仮想デストラクタ
下記のプログラムで、Bbb は、クラス Aaa から public派生したクラスです。
【下記プログラムをそのまま実行した場合】
main関数では、
Aaa* ptr = new Bbb;
として Bbb型オブジェクトを動的に生成し、
delete ptr;
で、それを破棄しています。
実行結果を先に示します。
この実行結果から、デストラクタ ~Bbb が呼ばれないままプログラムが終了していることがわかります。
ポインタ ptr の静的な型が Aaa*型なので、Aaa型のデストラクタが呼び出されてしまうのです。
その結果、動的に確保した配列 p 用の領域が解放されないままになってしまいます。
【下記プログラムで、デストラクタ ~Aaa を仮想デストラクタに変更した場合】
今度は、クラス Aaa のデストラクタを仮想デストラクタにしてみます。
具体的には、下記のプログラムの ★部分のコメントアウトを外して、
virtual ~Aaa();
とするだけです。
こうすることで、派生クラスのデストラクタ ~Bbb も、仮想デストラクタになります。
したがって、クラス Bbb は、仮想関数を含む多相的クラスということになります。
すると、
delete ptr;
によって呼び出されるのは、動的な型、つまりこの場合は、
ptr が指すオブジェクト型のデストラクタ ~Bbb です。
この場合の実行結果は次のようになります。
こんどは、終了時に、デストラクタ ~Bbb が呼ばれるので、配列の領域も解放されています。
以下は、プログラムです。
// ------------------------------------
#include <iostream>
using namespace std;
class Aaa {
public:
Aaa() {
cout << "constructor Aaa\n";
}
// virtual // ★
~Aaa() {
cout << "destructor ~Aaa\n";
}
};
class Bbb : public Aaa {
int* p;
public:
Bbb() {
p = new int[10];
cout << "constructor Bbb\n";
}
~Bbb() {
delete[] p;
cout << "destructor ~Bbb\n";
}
};
int main() {
Aaa* ptr = new Bbb;
delete ptr;
}
// ------------------------------------
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);
}
// ------------------------------------
2017年12月18日
《その187》 仮想関数,多相的クラス(前々回《185》の参考資料のみ)
前回《186》、参考資料を追加したついでに、m(_ _)m さらにあと一つだけ追加させてください。
前々回《185》のプログラムコードの、
関数 void print(Aaa& x);
関数 int main();
を次のコードに入れ替えると、前々回《185》のプログラムでは仮引数 x が参照でしたが、x がポインタであっても同じだということを確認していただけると思います。
// ------------------------------------
void print(Aaa* x) {
x->statement();
cout << typeid(*x).name() << '\n';
}
int main() {
Aaa obj_a;
Bbb obj_b;
Ccc obj_c;
print(&obj_a);
print(&obj_b);
print(&obj_c);
}
// ------------------------------------
結果は、前々回《185》と同じなので、省略します。
《その186》 仮想関数,多相的クラス(前回《185》の参考資料のみ)
《その185》 仮想関数,多相的クラス
仮想関数
まず、今回のプログラムの概略です。
基底クラス Base と、Base の public派生クラス Der1, Der2 があります。それぞれのクラスのメンバ関数 statement は仮想関数("virtual"が付加されている,関数名が同じ,仮引数が同じ)です。
main関数では、
Base型のオブジェクト bas
Der1型のオブジェクト de1
Der2型のオブジェクト de2
を作成し、各オブジェクトを引数にして、関数 print を呼び出します。関数 print は、それらのオブジェクトへの参照を受け取ります。
関数 print(Base& x); は、Base&型の仮引数で各オブジェクトへの参照を受け取ります。
そして、受け取った参照先オブジェクトの動的な型に応じた仮想関数 statement が呼び出されることになります。
bas の動的な型は Base型なので、呼び出されるのは Base::statement()
de1 の動的な型は Der1型なので、呼び出されるのは Der1::statement()
de2 の動的な型は Der2型なので、呼び出されるのは Der2::statement()
となります。
また、関数 print(Base& x); は、
各オブジェクトの型を、typeid演算子を用いて出力します。
typeid演算子が返す型については、下記の多相的クラスの項目をご覧ください。
多相的クラス
仮想関数を含むクラスは、多相的クラスと呼ばれる特殊なクラスです。
クラス Base は、仮想関数 statement を持っている
クラス Der1 は、仮想関数 statement を持っている
クラス Der2 は、仮想関数 statement を持っている
ので、すべて多相的クラスということになります。
多相的クラスであるため、関数 print(Base& x); の仮引数 x の参照先の型は、動的な型であると解釈されます。
typeid演算子は、その動的な型を返却します。
下記のプログラムの出力結果は、次のようになります。
また、もし、各クラスの virtual print(Base& x); から virtual を取り除いて、仮想関数ではない状態にした場合の、出力結果は、次のようになります。
// ------------------------------------
#include <typeinfo>
#include <iostream>
using namespace std;
class Base {
int a;
public:
Base(int x = 10) : a(x) { }
int get_a() const { return a; }
// 仮想関数
virtual void statement() const {
cout << "基底クラス Base\n";
}
};
class Der1 : public Base {
int b;
public:
Der1(int x = 20, int y = 22)
: Base(x), b(y) { }
int get_b() const { return b; }
// 派生クラスの "virtual" は省略可能です。
virtual void statement() const {
cout << "派生クラス Der1\n";
}
};
class Der2 : public Base {
int c;
public:
Der2(int x = 30, int y = 33)
: Base(x), c(y) { }
int get_c() const { return c; }
// 派生クラスの "virtual" は省略可能です。
virtual void statement() const {
cout << "派生クラス Der2\n";
}
};
void print(Base& x) {
x.statement();
cout << typeid(x).name() << '\n';
}
int main() {
Base bas;
Der1 de1;
Der2 de2;
print(bas); cout << '\n';
print(de1); cout << '\n';
print(de2);
}
// ------------------------------------