こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

継承したクラスを、継承元のクラス型の引数に渡すとどうなるのでしょうか?

継承したクラスを、継承元のクラス型の引数に渡すとどうなるのでしょうか?

以下のようなケースで、

#include "stdio.h"
using namespace std;

// baseクラス
class base {
private:
 int m_nII;
 int m_nJJ;
 int m_nKK;
public:
 base(int i,int j,int k){ m_nII=i; m_nJJ=j; m_nKK=k; }
 int GetSum(){ return (m_nII+m_nJJ+m_nKK); }
};

// base 継承クラス
class hoge : public base {
public:
 hoge() : base(1,2,3){}
};

void func(base* obj){ // baseクラスを引数に取る関数
 printf("sum is %d\n", obj->GetSum());
}

// main
int main(){
 hoge objHoge;
 func((base*)&objHoge); // <-キャストして渡す
 return 0;
}

として、一応、gccでコンパイルは通り、実行結果も期待通りだったのですが、
このやり方で問題は無いのでしょうか?
(たとえば継承先のクラスが独自のメンバを持っていたりなどした場合、期待した結果にならないとか・・)

よろしくお願いします。

投稿日時 - 2010-11-05 21:13:48

QNo.6299474

困ってます

質問者が選んだベストアンサー

言語仕様通りで問題ありません。

実行時の挙動の話をすると、
1.プログラムは関数と変数に分けられてメモリ上に配置される
プログラムがメモリにロードされて命令が実行される段階では、プログラムはテキスト領域、静的領域、ヒープ領域、スタック領域に分けられて実行されます。
テキスト領域には、関数が機械語に翻訳された内容が格納されますので、このことからメンバ関数を増やしても問題になりません。

2.メンバ変数は基底(継承元)クラスのメンバの後に継承クラスのメンバが継ぎ足しされる。

hoge のインスタンス(obj)のメモリ上の配置
1000から1019のアドレスにobjは格納される。
1000から1011のアドレスにobjのbaseクラスのメンバが格納される。
1012から1019のアドレスにobjのhogeクラスのメンバが格納される。
1000: base.m_nII; // base クラスのメンバ変数(obj)
1004: base.m_nJJ; // 〃
1008: base.m_nKK; // 〃
1012: hoge.m_anpan; // hoge クラスで追加したメンバ変数
1016: hoge.m_pizza; // 〃

3.メンバ関数は、構造体へのポインタを受け取る関数
「func」関数の引数で「base *obj」を指定し「obj->GetSum()」と呼び出すことは
「static int base::GetSum(base *obj)」の呼び出しと同じである。
つまり、構造体へのポインタを引数とする関数に等しい。

// 以上のことをC言語に直すとこんな風になります。
struct base { int m_nII, m_nJJ, m_nKK; };
struct hoge { struct base base; int m_anpan, m_pizza; };

void base_init(struct base *obj, int i, int j int k)
{ obj->m_nII = i; obj->m_nJJ = j; obj->m_nKK = k; }
int base_GetSum(struct base *obj)
{ return obj->m_nII + obj->m_nJJ + obj->m_nKK; }

void hoge_init(struct hoge *obj)
{ base_init((struct base *)obj, 1, 2, 3); obj->m_anpan = 120; obj->m_pizza = 3000; }
int hoge_GetPizza(struct hoge *obj)
{ return obj->m_pizza; }

int main() {
struct hoge obj; hoge_init(&obj);

printf("sum is %d\n", base_GetSum((struct base *)&obj));

obj.m_anpan = 1200;
printf("Anapn is \\%d\n", obj.m_anpan);
printf("Pizza is \\%d\n", hoge_GetPizza(&obj));

// アドレス表示
printf("obj.base.m_II : %x\n" &obj.base.m_II);
printf("obj.base.m_JJ : %x\n" &obj.base.m_JJ);
printf("obj.base.m_KK : %x\n" &obj.base.m_KK);
printf("obj.m_anpan : %x\n" &obj.m_anpan);
printf("obj.m_pizza : %x\n" &obj.m_pizza);

return 0;
}

ポイントは、クラスという概念は関数と構造体を一体的に扱う仕組みであり言語仕様上にのみ存在し、実行時にメンバ関数は単純な引数渡しと関数呼び出しになるということです。

関連として、関数のオーバーライド、ポリモルフィズムなどがありますが、それの実体は構造体のレコードに関数ポインタ配列へのポインタを持たせているだけです。
つまり理解するには、C言語の関数ポインタ、関数ポインタの配列について理解することが、遠回りですが近道になります。
# 理解しなくても使うことはできますが(ーー;)

あと、コードっぽいのは適当に書いただけなので間違いがあると思います。

参考URL:http://brain.cc.kogakuin.ac.jp/~kanamaru/lecture/MP/final/part06/node8.html

投稿日時 - 2010-11-06 00:54:56

お礼

内部的なことまで理解でき、大変感謝です。
ありがとうございました。

投稿日時 - 2010-11-06 11:28:52

このQ&Aは役に立ちましたか?

1人が「このQ&Aが役に立った」と投票しています

回答(2)

ANo.1

 全く問題ないですよ。よく使われる手法です。C++には欠かせないテクニックで、ポリモーフィズムを実現する方法の一つですね。

 継承先のクラスが同名の独自のメンバを持っている(今回はhogeクラスがint GetSum()を持っていた)場合に、どっちが呼ばれるかは、このソースではbaseの方になります。これを、hogeの方を呼ばせたければbaseのint GetSum()にvirtualというキーワードをつけます。

 この辺の詳しいお話は「オーバーライド」や「仮想関数(virtual)」などをキーワードに検索してみてください。

投稿日時 - 2010-11-05 23:32:28

あなたにオススメの質問