忍者ブログ

神戸電子専門学校ゲームソフト学科の生徒が運営するGESのブログです。

   

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

C++でクラスの不思議

ども、こんばんわ~

久しぶりに登場のZAKIです

学校ではラボにちょこちょこ出現していますけどねw

製作もそろそろ終盤

完成に向けてラストスパートをかけているころじゃないでしょうか

今年の2年生の作品は完成度が高いのが多そうなので楽しみです

寒いですが、風引かないように体調には気をつけましょうね


さて、今回は「C++でクラスの不思議」です。

まず、このコードを見てください。

#include <stdio.h>

//インターフェイスA
class A {
public:
 virtual void MesA() = 0;
};

//インターフェイスB
class B {
public:
 virtual void MesB() = 0;
};

//実装クラスC
class C : public A , public B {
public:
 void MesA() { printf("Aのメソッドです。\n"); }
 void MesB() { printf("Bのメソッドです。\n"); }
};

//メイン関数
int main()
{
 C  c;
 void * pVoid = NULL;
 B *  pB  = NULL;
 
 pVoid = (void *)&c;

 //普通にキャストした場合
 pB = (B *)&c;
 pB->MesB();

 //void *を通してキャストした場合
 pB = (B *)pVoid;
 pB->MesB();

 return 0;
}

さぁ、このコードを実行したとき、どういう結果が出るでしょうか?

「あれ?」と思うような結果になりませんか?

拍手[0回]



実行結果
Bのメソッドです。
Aのメソッドです。

となりませんでしたか?

おかしいですねぇ…Bクラスのポインタにダウンキャストしているので、

クラスでオーバーライドしたBのMesB()メソッド(実行結果:Bのメソッドです。)

が実行されるはずですよね?

どうして前はうまく言って、後は失敗、しかもAクラスのオーバーライドメソッドが呼ばれるのでしょうか

唯一の違いと言えば、変数cのアドレスがvoid *型にキャストされているかいないかです

では、どうしてvoid *型にキャストするとAのほうのメソッドを呼んでしまうのでしょうか

実はこの2つは「キャストの種類」「Vテーブル」というものの問題が絡んできます

まず、Vテーブルとは。

仮想関数を作ると「Vテーブル」というアドレス配列がクラスに作られます。

これは仮想関数の呼び出し先アドレスを保存するもので、

オーバーライドしたメソッドのアドレスが入っています。

Vtable.PNG

ちょっと余談

クラスを定義するとき、デストラクタは仮想関数にするべきだという話がありますね

その場合でも、Vテーブルは作成されるのですが、

そのクラスが継承される可能性が全く無い場合、そのVテーブルの分のメモリは無駄ということになります。

なので、考えなしにデストラクタを仮想関数にすることはよくない、と言う話を聞いたことがあります

さて、本題に戻りましょう

次はキャストの種類です

まず、前者の変数cのアドレスをB *型にキャストする場合、

これはダウンキャストと呼ばれるキャストになります

C++の演算子ではdynamic_cast<>演算子を使うやつですね

派生クラスの機能を基底クラスの機能へと機能の制限をすることができます

この場合、変数cのアドレスはCクラスのアドレスと言うことが型からわかるので、Cクラスの定義からVテーブルを参照し、

オーバーライドメソッドMesB()を呼びます

まぁ、いたって普通の出来事ですねw

問題は次のキャストです

このキャストもダウンキャストかといえば、違います

このキャストはポインタの型キャストになります。

C++の演算子ではreinterpret_cast<>演算子を使うほうですね

これもダウンキャストなんじゃないの?と思われるかもしれませんが、

この場合、変数pVoidは型なしであり、中身の値は何なのかすら判別できません

もちろん、中身がCクラスのアドレスであることもわからないので、

Bクラスのポインタへ強引にキャストしていることになります

頭の中ではそれでもうまく動きそうな気はしますねw

では、実際にMesB()メソッドへアクセスしようとしましょう。

まず、仮想関数なのでVテーブルを見に行きます

Bクラスには仮想関数が1つしかないので、Vテーブルの要素も1つしかないことになります

なので、Vテーブルの0番目の要素にアクセスします

しかし、クラスのアドレスはCクラスの変数cのものです

つまり、Vテーブルの要素は実は2つ(MesAメソッドとMesBメソッド)で、

0番目の要素にはAクラスの仮想関数をオーバーライドしたメソッドのアドレスが入っているのです

なので、実際呼び出されるメソッドはAクラスのMesA()をオーバーライドしたメソッドです

ということで、実行結果のような呼び出され方をしたわけです。

cast.PNG

はい、奥深いC++クラスの不思議でしたね~

冬休み前に悩んでいたところ、菖蒲先生に教えて頂いて、解決した問題です

いやはや、さすがにVテーブルまでは頭が回りませんでしたw

まだまだ、精進が足りませぬ

C++は奥が深くてやりがいがありますw

さて、それでは今回はこの辺りで。

あでゅーノシ
PR

COMMENT

NAME
TITLE
MAIL(非公開)
URL
EMOJI
Vodafone絵文字 i-mode絵文字 Ezweb絵文字
COMMENT
PASS(コメント編集に必須です)
SECRET
管理人のみ閲覧できます

ブログ内検索

最新コメント

[01/29 人面犬]
[10/01 8ch]
[09/12 uncle]
[09/10 某卒業生]
[06/07 uncle]

カレンダー

04 2024/05 06
S M T W T F S
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

テスト

Copyright ©  -- GESブログ --  All Rights Reserved
Design by CriCri / Photo by Geralt / powered by NINJA TOOLS / 忍者ブログ / [PR]