はじめまして、こんにちわ。
いつも参考にさせて頂いております。
C++の基本である「継承」について独自で勉強しているのですが
なぜ、このような結果になるのか理解できず質問させて頂きました。
少々、内容が長くなってしまい申し訳ありませんがご教授お願い致します。
以下に作成したコードを記載します。
#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <conio.h>
#define MAXIDSIZE 5
class A{
protected:
char id[MAXIDSIZE];
public:
A(void) {
strncpy(id, " ", MAXIDSIZE);
id[MAXIDSIZE-1] = '\0';
}
virtual void foo(void) {
printf("CLASS-A VIRTUAL FOO.\n");
}
void hoge(void) {
printf("CLASS-A HOGE. %d\n", id);
}
};
class B :public A {
public:
B(void) {
strncpy(id, " ", MAXIDSIZE);
id[MAXIDSIZE-1] = '\0';
}
void foo(void) {
printf("CLASS-B FOO.\n");
}
void hoge(void) {
printf("CLASS-B HOGE.\n");
}
};
class C :public B {
public:
C(void) {
strncpy(id, " ", MAXIDSIZE);
id[MAXIDSIZE-1] = '\0';
}
void hoge(void) {
printf("CLASS-C HOGE.\n");
}
};
void main(void) {
A a, a1, a2;
B b;
C c;
A *pa0, *pa1, *pa2;
a.hoge(); /* [01] */
b.hoge(); /* [02] */
c.hoge(); /* [03] */
printf("\n");
a1 = b;
a2 = c;
a.hoge(); /* [04] */
a1.hoge(); /* [05] */
a2.hoge(); /* [06] */
printf("\n");
pa0 = &a;
pa1 = &b;
pa2 = &c;
pa0->hoge(); /* [07] */
pa1->hoge(); /* [08] */
pa2->hoge(); /* [09] */
printf("\n");
pa0->foo(); /* [10] */
pa1->foo(); /* [11] */
pa2->foo(); /* [12] */
getch();
}
上記の通り、クラスA(基本)、クラスB(Aからの派生)、クラスC(Bからの派生)があり、
3クラス共通の関数 HOGE() と2クラス共通の関数 FOO() があります。
この処理の実行結果は、
CLASS-A HOGE. 1245048
CLASS-B HOGE.
CLASS-C HOGE.
CLASS-A HOGE. 1245048
CLASS-A HOGE. 1245036
CLASS-A HOGE. 1245024
CLASS-A HOGE. 1245048
CLASS-A HOGE. 1245012
CLASS-A HOGE. 1245000
CLASS-A VIRTUAL FOO.
CLASS-B.
CLASS-B.
となります。
コメント番号:[01][02][03]
ここについては、インスタンス a,b,cはそれぞれのクラスによって定義されている
おり、かつ HOGE()は3クラス共通に存在するので、それぞれのクラスのHOGE関数が実行されています。
コメント番号:[04][05][06]
理解できていないのが、ここのブロックなのですが
[04]は、わかります。
しかし、[05][06]については、クラスBの実体をクラスAのインスタンスに代入した結果を、
クラスCの実体をクラスAのインスタンスに代入した結果を・・・と考えていたのですが、
結果をみるとそうではありませんでした。
私の予想では、CLASS-B HOGE.+アドレス CLASS-C HOGE.+アドレス が
結果となるのでは??と思っていたのですが・・・
(結果的にこの考えは間違っていたのですが、その理由がわかりません)
「a1 = b;」
「a2 = c;」
について、何を行なっているのかをご教授お願い致します。
これと同じことが言えるのがコメント番号[07][08][09]です。
各クラスで定義されたインスタンスのアドレスを渡しているにも関わらず、
なぜ基本クラスであるAの HOGE関数が呼ばれてしまうのでしょうか。
(というより、いつの間に、インスタンスb,cがクラスAのインスタンスになって
しまったのか・・・)
かなり、長くなってしまい申し訳ありませんが以上の点につき、
ご教授お願い致します。
※[12]の結果は理解しています。
クラスCには FOO関数が存在しない為、派生元であるクラスBのFOOが
呼び出されていると理解しています。
「a1 = b;」
「a2 = c;」
については、bの内容をa1にコピーしているだけであって、a1がクラスAであることには変わりません。
[07]以降について、hogeとfooの違いは、関数が、virtual(仮想関数)であるかどうかです。
hogeは仮想関数ではないので、呼ぶときのインスタンスの型で呼ぶ関数が決まります。
fooは仮想関数ですので、作成した時のインスタンスの型で呼ぶ関数が決まります。
仮想関数について、調べてみてください。
最初の質問は難しいので後回しにして、後者のほうを先に。
7,8,9 は A* pa1=&b; pa1->hoge(); ですね。
非仮想なメンバ関数はソースコードの見た目の型のものが呼ばれる。
仮想なメンバ関数はソースコードの見た目によらず実際の型のものが呼ばれる。
というふうに C++ の仕様が決まっているので、そう動作します。
この例の場合、関数呼び出しは pa1 によって行われています。
pa1 の型は A* なので A::hoge が呼ばれます。
>(というより、いつの間に、インスタンスb,cがクラスAのインスタンスになって
>しまったのか・・・)
インスタンスは変わっていません。
基底クラスへのポインタで派生クラスのインスタンスを指しているだけです。
基底クラスへのポインタは派生クラスのインスタンスを指せるのです。
逆はダメ。
11 は仮想なメンバ関数を呼んでいるので、ソースコードの見た目の型 (pa1 の型つまり A*)
によらず実体である B の関数 B::foo が呼ばれています。
a1=b; a2=c; は理解が難しい話になります。ここでは「スライシング」が起きています。
a1 も a2 も A 型なので、どう転んでも B 型や C 型の内容を入れることはできません。
なので b のうちの A の部分だけ、 c のうちの A の部分だけが a1 a2 に入れられています。
a1 も a2 も A 型なので A::foo A::hoge が呼ばれるのが正しい動きとなります。
予期せぬスライシングはバグの元なので、意図してそう書いたのならかまいませんが、
a1=b; のようなコードは書くべきではありません。
ツイート | ![]() |