親クラスのポインタで派生クラスの関数呼び出し

このQ&Aのポイント
  • 親クラスのポインタで派生クラスの関数を呼び出すと、派生クラスの関数が呼ばれることがあります。
  • 派生クラスのポインタ型にキャストすると、派生クラスのメンバ関数が利用可能になります。
  • ただし、親クラスのオブジェクトを指すポインタ経由で派生クラスのメンバ関数を呼び出す場合、派生クラスのコンストラクタが呼ばれないため注意が必要です。
回答を見る
  • ベストアンサー

親クラスのポインタで派生クラスの関数呼び出し

下記のようなクラス構成があるとします。 説明のため簡略化しています。 [Test.h] class CParent { protected: int nParent; public: CParent(){nParent = 10;} ~CParent(){}; virtual int func1(){ // (*1) return nParent; } }; class CSub : public CParent { int m_nSub1; // 派生クラスにしかないメンバ変数 int m_nSub2; // 派生クラスにしかないメンバ変数 public: CSub(){m_nSub = 20;} ~CSub(){}; int func1(){ // (*3) return nParent*2; // 20 } // 派生クラスにしかないメンバ関数 int func2() // (*4) { m_nParent; // 10 return m_nSub1; // 不定値 } int SetData(int n) // (*5) { m_nSub2 = n; return m_nSub2; } }; [Test.cpp] void main() { CParent parent; // 親クラスのオブジェクト CParent* pParent = &parent; // 親クラスのオブジェクトを指すポインタ int nRet = 0; nRet = pParent->func1(); // (*1) // サブクラスのポインタ型にキャストする CSub* pSub = (CSub*)pParent; // (*2) nRet = pSub->func1(); // (*3) nRet = pSub->func2(); // (*4) nRet = pSub->SetData(60); // (*5) } (*3)ではpSubの指すオブジェクトが親クラス(CParent)のため、ポリモーフィズムのメカニズムに従ってCParent::func1()が呼ばれ10が返ってきます。ここまでは想定通りです。 次の(*4)ですが、これはCSub::func2()が呼ばれています。なぜこの呼び出しで子クラスの関数を呼ぶことが出来るのでしょうか? 実際その動作をさせたいのですが、なぜそうなるのかが分かりません。想定ではpSubが指す先は親クラスのため、子クラスにしかないメンバ関数func2()は呼べないはずです。 このコードでは、CSubのコンストラクタは通りませんので(そもそもCSub型のインスタンスは実体化していない)、(*4)の呼び出しでは不定値が返ります。また、(*5)は期待通り動作し、戻り値60が返っています。 これは危険な事をしているのでしょうか? 期待通りではあるのですが、なぜこのような動作になるのか理解できません。 上記のようなことをしたい場合、どのように記述するべきでしょうか?

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

  • ベストアンサー
  • wormhole
  • ベストアンサー率28% (1621/5657)
回答No.3

>前の方のお礼コメントにも書きましたが、func2が呼べるのが謎です。 謎も何も、そのC++処理系がそのようなコードを出力する処理系だというだけの事です。 例えばint CSub::func()が次のようなCソースと同等にコンパイルされ int CSub_func2(CSub *this) { return this->m_nSub1; } CSub::func2()の呼び出しが nRet = pSub->func2();// (*4) ↓ nRet = CSub_func2(pSub); のようなコードと同等ならばpSubの値がCParentのインスタンスだろうがCSubのインスタンスだろうが関係なくCSub::func2()が呼び出されます。

katorea21
質問者

お礼

なるほど、そういうコードを出力するのですか。直感的には理解しがたいですね。 pSubポインタの指し示す先にあるオブジェクトの中のメンバ関数テーブルにfunc2はないはずだから、コンパイルは通っても実行時エラーになるというのが直感的な感覚です。 ともあれ、このようなコードはたとえ動作しても全く無意味であることが分かりました。 ありがとうございました。

その他の回答 (4)

回答No.5

> pSubポインタの指し示す先にあるオブジェクトの中のメンバ関数テーブルにfunc2はないはずだから、 関数テーブルは、仮想関数のようにオブジェクトによって呼び出す関数が決まる仕組みを実装するためのもので、すべての関数がテーブルを使って呼び出されるわけではありません。そうでないと、派生クラスの仮想関数から基底クラスの同名関数を呼び出しできなくなりますよね。

katorea21
質問者

お礼

そうですね。仮想関数でない普通のメンバ関数はコンパイル時に静的に呼び出すべき関数が決まるから、オブジェクトに関数テーブルを持っているのではないですね。 本来このような関数呼び出しはコンパイル時に警告が出ても良さそうですが、それが普通に通るのがC++なんですかね。

回答No.4

> CParentの実体を指しているポインタ経由でCSubにしかないメンバ関数をなぜ呼び出せるのでしょう? どんな挙動を示そうが、未定義な挙動に理由はありません。 ...とはいえ、一般的な実装では以下のようなことが起きています. int CSub_func2(CSub* this) { ... } // int CSub::func2() int main() { CParent parent; CParent* pParent = &parent; CSub* pSub = (CSub*)pParent; CSub_func2(psub); // pSub->func2() と等価 }

回答No.2

> なぜこの呼び出しで子クラスの関数を呼ぶことが出来るのでしょうか? ポインタをキャストした場合、元のオブジェクトが何かはチェックされません。 pSubはCParentだから、func2という関数は存在しないはず、とお考えかもしれませんが、コンパイラ的にはCSubクラスのfunc2という関数を呼び出し、その際にthisにpSubを設定しているだけです。 不定値が返るのは、pSubがCSubのポインタだとするとm_nSub1があるはずの位置のデータを返すからです。SetDataは期待通り動作しているように見えますが、pSubがCSubのポインタだとするとm_nSub2があるはずの位置のデータを書き換えています。実際にはpSubはCParentですから、m_nSub2の場所は確保されていないので、何か別のデータが保存されている部分を書き換えていることになります。コンストラクタを通っているかどうかという問題ではありません。 CParentのポインタがあるとして、それが実際にはCSubかCSubの派生クラスのポインタとして使用して問題ないのかを確認したいのであれば、dynamic_castを使用することになるんじゃないかと思います。

katorea21
質問者

お礼

ご回答ありがとうございます。 前の方のお礼コメントにも書きましたが、func2が呼べるのが謎です。CSubクラスのfunc2の実体はどのタイミングでメモリ上にロードされるのか? func2の呼び出しより前にロードされているとして、なぜCParentを指しているポインタ経由ででCSub::func2を正しくポイント出来るのか?

回答No.1

「やってはいけない」ことをやってます。 このときの動作は"未定義"なので、どんなふるまいをしてもそれに理由などありません。 > 上記のようなことをしたい場合、どのように記述するべきでしょうか? CSub child; CParent* pParent = &child;

katorea21
質問者

お礼

ご回答ありがとうございます。 やはりやってはいけないことですよね。これは想定通りでした。 ただ、なぜこのコードでfunc2が正常にコール出来ているのかが分かりません。CSubにキャストしているとはいえ、CParentの実体を指しているポインタ経由でCSubにしかないメンバ関数をなぜ呼び出せるのでしょう?

関連するQ&A

  • C++ クラスをメンバにもつクラスについて

    お世話になります。C++初心者でうまくコードが書けません(><) クラス1と2があり、クラス1のメンバにはクラス2があります。 メインでクラス1をインスタンス化してクラス2のfunc2を呼び出します。 func2ではクラス1のインスタンスから呼び出された場合にクラス1の m_int1を取得します。 Class Class1{ public:  int m_int1;  Class2 m_Class2; }; Class Class2{ public: void func2(); }; void Class2::func2(){  /*ここの記述方法が分かりません*/  /*C1から呼び出されたらC1のm_int1に100を入れる*/  /*以下間違え*/  class1 C2_1;/*別のclass1のオブジェクトなのでこれに入れてもダメっぽい*/  C2_1.m_int1 = 100; } void main(){  class1 C1;  C1.m_int1 = 10;  C1.m_class2.func(); } C1.m_class2.func()の中から呼び出したC1にアクセスする方法が 分かりません(TT)。実体がまだあるのだからアクセスする方法は あると思うのですが・・・ どなたかよろしくお願いします。

  • 基本クラスのポインタで、派生クラスのメンバ関数を呼び出す方法?

    VC++でプログラミングをしています。 A(基本クラス) B(派生クラス) を作成しました。Bは、Aの特別な場合です。 このとき、基本クラスAのポインタから、派生クラスBにのみあるメンバ関数を呼ぶことはできないのでしょうか? 基本クラスAにも同じ名前の関数があれば、仮想関数をオーバーロードすれば呼び出せるようですが、この関数は、基本クラスには不要なので、できれば使わないメンバ関数を基本クラスに書きたくありません。 (純粋仮想関数という方法もあるようですが、) 操作としましては、 Aのポインタ配列 A* a[100]を作成し 特別な場合のみ派生クラスBのメンバ関数だけを実行させたいのです。 派生クラスにのみあるメンバ関数を、Readとします。 for(i=0;i<100;i++){ if(派生クラスBの場合){ a[i]->Read() } } 現状では、コンパイルエラーで、 関数Readは、aのメンバ関数ではありませんとなってしまいます。 以上よろしくお願いします。

  • 【C++】関数ポインタの使い方

    関数ポインタの使い方で悩んでいます。 下記の (1)のようにグローバルメソッドとして定義したメソッドを関数ポインタに代入することは出来るのですが、 (2)のようにクラスのメンバメソッドとして定義したメソッドは関数ポインタに代入することは出来ませんでした。 Error:バインドされた関数へのポインターは関数の呼び出しにのみ使用できます。 というエラーが発生します。 関数ポインタに外部参照でメソッドを代入することは出来ないのでしょうか? -----(1)------------------------------------------------------------------ #include "stdafx.h" #include <iostream> using namespace std; int f(int a, int b){ return a * b; } int _tmain(int argc, _TCHAR* argv[]) { typedef int (* FUNC_POINTER)(int, int); FUNC_POINTER fp; fp = f; cout << fp(1,2) <<endl; getchar(); return 0; } ------------------------------------------------------------------------- -----(2)------------------------------------------------------------------ #include "stdafx.h" #include <iostream> using namespace std; class MPointerList{ public: int f(int a, int b){ return a * b; } }; int _tmain(int argc, _TCHAR* argv[]) { typedef int (* FUNC_POINTER)(int, int); FUNC_POINTER fp; //fp = f; MPointerList mP; fp = mP.f; cout << fp(1,2) <<endl; getchar(); return 0; } -------------------------------------------------------------------------

  • 派生クラスのメンバを基底クラスの参照に代入(C++

    文末のコードのように、 基底クラスで、派生クラスのメンバの参照を持つのはまずいでしょうか。 (classではなくstructにしているのは質問上でのpublic:の省略のためだけです) 初期化順序的には、基底クラスの参照先は、 基底クラスのコンストラクタが走る時点で初期化されていないので、 コンストラクタ内で参照に対して何かしようとすると問題になると思っています。 基底クラスのコンストラクタ内で派生クラスメンバの参照に対して何かしなければ、 参照は有効で、派生クラスのコンストラクタ実行後であれば 問題なく動くと思ってよいでしょうか。 struct A { int& m_ref; A(int& ref) : m_ref(ref) { } }; struct B : public A { int m_obj; B() : A(m_obj) { } };

  • クラスのメンバ関数の呼出しについて

    こんにちは。 初心者ですが、よろしくお願いします。 MFCを使用してダイアログベースのアプリケーションを作成しています。 <ヘッダ> class Sample : public CDialog { (略) public: int SampleA(CString); (略) <ソース> UINT SampleB(LPVOID pParam) { int n; n = Sample::SampleA(CStringA) ←エラー return 0; } メンバ関数ではない、SampleBから、上記のように呼出すと、「静的でないメンバ関数の中で呼び出しが正しくありません。」というエラーが表示されてしまいます。 ヘッダ内のメンバ関数宣言をstaticにすれば問題は解決できるのですが、このやり方しかないのでしょうか。 よろしくお願いします。

  • C++ オート変数にポインタを代入したら?

    処理の初めに、オブジェクトTestClassにデータm_intDataをセットします。その後、ループ内の関数Func内で、m_intDataを取得し、それを元にいろいろな処理をします。この時、Func内でのオート変数intDataTestにm_intDataのポインタを代入しているのですが、Funcを抜けた時にintDataTestがスコープから外れるために、intDataTest = m_intDataも開放されてしまうのでしょうか?もしそうなら、2度目にFuncを呼び出した時は、m_intDataは不定値になっていて、2度目のFuncを抜ける時、すでに開放した領域を開放しようとしてエラーになりますか? もっと複雑な本物コードを動かすと、1度目のFunc呼び出しでは有効値が入っていたintDataTestが、2度目では、すべて0になってしまっていました。 本当は、Funcが何回呼び出されても、最初に設定したintDataTestのデータを読み込みたいのですが、どのように直せばよいのかわからず、困っています。 また、私の理解が間違っていましたら、正しい解釈を教えて下さい。 //------------------------------------------------------------------------- class TestClass { private:   int* m_intData; public:   TestClass()   {     m_intData = NULL;   }   ~TestClass()   {     if( m_intData != NULL )     {       delete m_intData;       m_intData = NULL;     }   }   void SetData( int* intData )   {     m_intData = intData;   }   int* GetData()   {     return m_intData;   } }; //------------------------------------------------------------------------- void Func( TestClass* pClass ) {   int* intDataTest = pClass->GetData();   // この後、いろいろな処理 } //------------------------------------------------------------------------- int main() {   int* intData = NULL;   intData = new int[10];   for( int i = 0; i < 10; i++ )   {     intData[i] = i;   }   TestClass* pClass;   pClass->SetData( intData );   for( int i = 0; i < 5; i++ )   {     Func( pClass );     // この後、複雑ないろいろな処理   }   return  1; } //-------------------------------------------------------------------------

  • 多態性を利用して派生クラスの関数を呼びたい

    環境はVS2013,言語はC++です. 基底クラス側で,基底クラスのポインタを引数に受ける仮想関数を宣言し, 派生クラスでその実装をします. 派生クラス側で引数に派生クラスのポインタを受けて,派生クラスのメンバにアクセスできるようにしたいのです. 作成したサンプルを下記に示します. Hello()は関係ありません. また,これはあくまで簡略化したサンプルなだけなので,thisポインタからメンバにアクセスすればよいというものではありません. #include <iostream> class Base { public: int i; Base() :i(0) {} virtual void print(Base *p) { std::cerr << "Base : " << i << std::endl; } virtual void hello() = 0; }; class Super : public Base { public: int x; Super() : x(1) {} void print(Super *p) { std::cerr << "Super : " << p->x << std::endl; } void hello() { std::cerr << "hello" << std::endl; } }; int main(void) { Base *p; p = new Super; p->print(p); } 上記を実行した結果,Base::print()が呼び出されました. print関数の引数に派生クラスSuperの実態を差すBase型ポインタを与えたときに,Super::print()を呼び出せるようにするには,何か方法はありますか. p->print(p)の呼び出し部分をp->((Super*)p)にキャストしても結果は同じくBase::print()が呼ばれました. Base::print()の実装を無くした場合は,未解決の外部参照のコンパイルエラーが発生し, Base::print()を純粋仮想関数にした場合は,Super側でSuper::pirnt(Base*p)を実装しなければならないのか,抽象クラスのインスタンス化ができないというコンパイルエラーが発生します. これは,派生クラスとはいえ,引数リストの型が違うのでオーバーライドされていないということですよね. 引数をBase *p,Super *pからそれぞれvoid *pへ変更し,Super::print()内で(Super *)へキャストしなおしたところ,きちんとオーバーライドされ,うまくはいきました. しかしながら,この方法だと派生クラス側で実装する際に毎回キャスト処理を書かなくてはなりません. 他に何かきれいな方法はないでしょうか.

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

    継承したクラスを、継承元のクラス型の引数に渡すとどうなるのでしょうか? 以下のようなケースで、 #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でコンパイルは通り、実行結果も期待通りだったのですが、 このやり方で問題は無いのでしょうか? (たとえば継承先のクラスが独自のメンバを持っていたりなどした場合、期待した結果にならないとか・・) よろしくお願いします。

  • 関数ポインタについて

    C言語の関数ポインタの問題で以下のような問題で、実際に解いて、プログラムを動かしてみてみました。 正常に動作したのですが、この回答では満点はもらえませんでした。 このほかに良い解答例などありましたら、教えていただけないでしょうか。 どうかよろしくお願い致します。 [問題]次の※1・※2を埋めなさい(「func1」,「func2」は解答に含まれないように書くこと)。 #include <stdio.h> void func1() { printf("func1\n"); } void func2() { printf("func2\n"); } void func(int no) { void (*func[2])(void) = {func1, func2}; /* ※1 (*func[no])() */; } int main() { /* ※2 func(1) */; return 0; } 実行結果:func2

  • クラス内の関数内static変数について

    クラス内の「staticではないメンバ関数内で定義される」static変数の初期化タイミングはいつでしょうか? 自分としてはクラスのインスタンス生成時に初期化されるものだと思っていたのですが、どうもそうでは無さそうだという現象に出会ったもので。 例えば以下のようなサンプルプログラムがあるとします。 --------------------------------------- class TA { public: void func(int i); }; void TA::func(int i) { static int d=0; d += i; std::cout << d << std::endl; } int main() { for(int i=1; i < 3;i++) { TA ta; ta.func(i); ta.func(i); ta.func(i); } } --------------------------------------- これを実行した時、自分としては 1 2 3 2 4 6 という結果を期待していた訳ですが、実際には 1 2 3 5 7 9 という結果になりました。 ということは、もしかしてメンバ変数ではなくともクラス内に現れるstatic変数はstaticなメンバ変数と同等ということなのでしょうか? 実際、上記ソースのforループ内にもう一つclass TAのインスタンスtbを追加してみると、 --------------------------------------- for(int i=1; i < 3;i++) { TA ta; ta.func(i); ta.func(i); ta.func(i); TA tb; tb.func(i); tb.func(i); tb.func(i); } --------------------------------------- 1 2 3 4 5 6 8 10 12 14 16 18 となりました。 (まぁstaticではないメンバ変数に置き換えれば一応解決するのですが、個人的に何か凄く気持ち悪く感じて・・・)

専門家に質問してみよう