• 締切済み

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

環境は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 *)へキャストしなおしたところ,きちんとオーバーライドされ,うまくはいきました. しかしながら,この方法だと派生クラス側で実装する際に毎回キャスト処理を書かなくてはなりません. 他に何かきれいな方法はないでしょうか.

みんなの回答

  • a_kwn
  • ベストアンサー率34% (8/23)
回答No.3

> 基底クラス側で,基底クラスのポインタを引数に受ける仮想関数を宣言し,派生クラスでその実装をします. > 派生クラス側で引数に派生クラスのポインタを受けて,派生クラスのメンバにアクセスできるようにしたいのです. このこと自体が、すでに設計としておかしいと分かっていて、敢えてそのようにしたいのでしょうか? 動物という基本クラスがあって、猫、豚、人間の3つの派生クラスがあったとしましょう。 そこに(本当はナニと書きたいがここでは上品に)キスという関数を作るとしましょう。 class 動物 { public: void kiss(動物* p); // 1) }; という関数を作ったら、《猫、豚、人間》が《猫、豚、人間》にキスができなければなりません。どんな組み合わせであってもです。 (例えば、猫が豚にキスができなければ、そもそもこの関数を定義すること自体がおかしい) ですが、あなたは、 class 猫 : public 動物 { public: void kiss(猫* p); // 2) }; という、猫が猫にキスする関数と、1)を必死に関連付けしようとしていますが、そもそも1)と2)は全く異なるものだということは理解されているのでしょうか? もし、本当に1)のすべての組み合わせを作りたくて、1)の関数の実装内で、猫、猫の組み合わせのときに、2)を呼ぶ方法が知りたいといことであれば、例えばダブルディスパッチの実装パターンが使えます。

marriess
質問者

補足

>>すでに設計としておかしいと分かっていて、敢えてそのようにしたいのでしょうか? はい,承知しています. すみません,質問文をなるべく簡単にしようとして,情報を切り捨てすぎてしまいました. 特定のアルゴリズムを提供するクラスを作成したいのですが,アルゴリズムが取り扱うオブジェクトは,質問文中ではBaseクラスにあたる抽象クラスを継承し,アルゴリズム内で仮想関数(たとえば演算関数)を通じてオブジェクトの情報を取得します. アルゴリズムの利用時には,派生クラスは1種類しかなく,質問文中でのprint(Base *p)の*pは必ずSuperの実態を差すという条件で実装をしています. ただ,演算関数を実装する際に,呼び出したthis自身はSuperのメンバへはアクセスできますが,演算相手はBase *pとして認識するので,Super型のメンバにアクセスすることができないという問題がありました. アルゴリズムのクラスをテンプレートクラスとして記述することで解決はできますが,アルゴリズムの実装をすべてヘッダに記述しなければならず,このせいで実装を隠ぺいできないことと,コンパイルに時間がかかることを嫌って,アルゴリズムクラスはスタティックライブラリに隠ぺいし抽象クラス(インターフェース)を通じてオブジェクトの情報を得る,ということをしようとしていました. 引数のポインタは必ずSuperの実態を指すように実装したので,質問文で記述したように引数をvoid*pにしてキャストしたり,他の回答者様がおっしゃられているように引数のBase*pをキャストしたりすることで解決はできます.ただ,これだとアルゴリズムクラスの利用者が毎回キャストのコードをすべての関数実装時に記述しなければならないため,これを省略するためにprint(Base *p)が呼ばれたら,pが指す実体の型に合わせて一発でprint(Super *P)を呼ぶ方法がないかと思い,今回の質問をさせて頂きました. ご紹介いただいた「ダブルディスパッチ」という手法は初めて知りました.オーバーロードのしくみを利用し,実体の型に合わせて呼び出す関数を任意に操作できるということで,とても興味を引きつけました. 今回の問題の解決に繋がるかはまだわかりませんが,より詳しく調べて試してみようと思います. 回答ありがとうございました.

全文を見る
すると、全ての回答が全文表示されます。
回答No.2

Base::print()は純粋仮想関数でなくても動作しました。このコードで試してみました。おそらく、質問された方の意図に適っているでしょう。 #include <iostream> class Base { public: int i; Base() :i(0) {} virtual void print(Base *p) { std::cerr << "Base : " << i << std::endl; } }; class Derived1 : public Base { public: int x; Derived1() : x(1) {} void print(Base *pp) { Derived1* p =static_cast<Derived1*>(pp); std::cerr << "Derived1 : " << p->x << std::endl; } }; class Derived2 : public Base { public: double y; Derived2() : y(2) {} void print(Base *pp) { Derived2* p = static_cast<Derived2*>(pp); std::cerr << "Derived2 : " << p->y << std::endl; } }; int main(void) { Base *p1, *p2, *p; p1 = new Derived1; p1->print(p1); p2 = new Derived2; p2->print(p2); p = p1; p->print(p); p = p2; p->print(p); }

全文を見る
すると、全ての回答が全文表示されます。
回答No.1

Base::print()を純粋仮想関数にしてもよいのなら、Super::print()の引数を型変換するだけでよいのではないでしょうか。 class Super : public Base { void print(Base *pp) { Super* p = (Super*)pp; std::cerr << "Super : " << p->x << std::endl; } }

全文を見る
すると、全ての回答が全文表示されます。

関連するQ&A

  • ラムダ式を関数に渡したいのですが

    Class1からClass2にある関数の引数にラムダ式で関数を渡すようなプログラムを作ろうと思い、以下のようにコードを記述しました。 #include <iostream> class Class1 { int i = 0; void func() { std::cout << "Hello world" << std::endl; } void init() { Class2 *class2 = new Class2(); class2->setFunc([this] { func(); i++; }()); } }; class Class2 { void(*mFunc)(); void runFunc(){ mFunc(); } public: void setFunc(void func()) { mFunc = func; } }; すると class2->setFunc([this] { func(); i++; }()); のところで「型"void"の引数は型"void(*)()"のパラメーターと互換性がありません」というエラーが発生してしまいます。 キャプチャにthisを渡したラムダ式を他のクラスの関数ポインタのような変数に代入させるようにしたいのですがどのように記述したらできますか

  •    ダイアログのクラス化で仮想関数を用いて派生クラスにしているんです

       ダイアログのクラス化で仮想関数を用いて派生クラスにしているんですが・・・ ダイアログを基本クラスで静的プロシージャと派生クラスでオーバーライドしてプロシージャを使いたい のですが、どうしても自身のポインタが取得できません。 以下にソースを載せておきます。  class CBaseWnd  {  public:    // ポインタの設定    void SetPointer( HWND hWnd );    // ウィンドウプロシージャの呼び出し    static LRESULT CALLBACK CallProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );    // ウィンドウプロシージャの実装    virtual LRESULT MainProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );  }; [クラスの実装]  //===== ポインタの設定 =====//  void CBaseWnd::SetPointer( HWND hWnd )  {    SetWindowLong( hWnd, GWL_USERDATA, (LONG)this );  }  //===== ウィンドウプロシージャの呼び出し =====//  LRESULT CALLBACK CBaseWnd::CallProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )  {    //_プロパティリストからthisポインタを取得 //ここでポインタを取得することができないでいます。値が0です。 //先にSetWindowlongをやっても値が0のままです。    CBaseWnd* thisPtr = (CBaseWnd*)GetWindowLong( hWnd, GWL_USERDATA );    //_thisポインタが取得できなかった場合...    if( ! thisPtr )    {      //_ウィンドウの作成時の場合... //ここでアクセス違反というエラーが起きる      if( message == WM_INITDIALOG )        thisPtr = (CBaseWnd*)((LPCREATESTRUCT)lParam)->lpCreateParams;      //_thisポインタが取得できた場合...      if( thisPtr )      {        //_プロパティリストにオブジェクトハンドル(thisポインタ)を設定する        thisPtr->SetPointer( hWnd );      }    }    //_thisポインタが取得できた場合...    if( thisPtr )    {      LRESULT lResult = thisPtr->MainProc( hWnd, message, wParam, lParam );      return lResult;    }    return DefWindowProc( hWnd, message, wParam, lParam );  }  //===== ウィンドウプロシージャの実装(継承可能) =====//  // ここでの記述はデフォルトの処理  //  LRESULT CBaseWnd::MainProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )  {    switch( message )    {      //_ウィンドウが破棄された場合      case WM_DESTROY:        PostQuitMessage(0);        return 0;        //_デフォルトの場合      default:        return DefWindowProc( hWnd, message, wParam, lParam );    }  } WM_INITDIALOGでダイアログの初期化中にポインタを取得しようとしますが、アクセス違反が起こり失敗します。 どなたか分かる方がいらっしゃったらご指摘お願いします。

  • C++のクラスについて

    /*以下のコメントがある行では何故、コンストラクタ(class2::class2)を指定出来ないのですか? デストラクタ(class2::~class2)の場合も問題なくコンパイルが通り、実行できます (http://codepad.org/1oJkxjyZ の23行目) 開発環境 Windows XP SP3 コンパイラ:GCC 実行結果 class1のコンストラクタ class2のコンストラクタ aiueoの実行 class2のデストラクタ class1のデストラクタ */ #include<iostream> class class1; class class2; class class1{ public: class1(); ~class1(); private: class2*pointer; }; class class2{ public: class2(); ~class2(); void aiueo(); }; class1::class1(){ std::cout<<"class1のコンストラクタ"<<std::endl; pointer=new class2(); pointer->aiueo(); //aiueoを~class2に置き換えてもコンパイル出来るが、class2だとエラーが出る } class1::~class1(){ delete pointer; std::cout<<"class1のデストラクタ"<<std::endl; } class2::class2(){ std::cout<<"class2のコンストラクタ"<<std::endl; } class2::~class2(){ std::cout<<"class2のデストラクタ"<<std::endl; } void class2::aiueo(){ std::cout<<"aiueoの実行"<<std::endl; } int main(){ class1 test1; return 0; }

  • shared_ptr クラスについて

    shared_ptrクラスを使いたいのですが、使えません、どうしてでしょうか?ソースはこれです。 #include<iostream> #include <string> #include <fstream> #include<memory> using namespace std; class SMonster{ string name; int power; public: SMonster(); SMonster(int p); ~SMonster(){ }; void SetPower(int p); int GetPower(SMonster& t)const; void walk(const string& str); int GetPoint(void)const; }; class B {}; class D : public B {}; int main(void) { shared_ptr<D> sp0(new D); SMonster m(200); SMonster n(100); std::cout<<m.GetPower(m)<<std::endl; std::cout<<n.GetPower(n)<<std::endl; ShowWindow(10); }

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

    下記のようなクラス構成があるとします。 説明のため簡略化しています。 [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が返っています。 これは危険な事をしているのでしょうか? 期待通りではあるのですが、なぜこのような動作になるのか理解できません。 上記のようなことをしたい場合、どのように記述するべきでしょうか?

  • キャストについて教えてください

    基底クラスから派生クラスにポインタはキャストできないのでしょうか?? 以下のソースを実行したところすべてのキャスト演算において '初期化中' : 'CBase *' から 'CEx *' に変換できません。 base から derived へのキャストには、dynamic_cast または static_cast が必要です。 というエラーが出てしまいます。 これはキャストできないのでしょうか?? class CBase { public:     int base; }; class CEx : public CBase { public:     int ex; }; void main( void ) {   CBase* base = new CEx;   CEx* e1 = dynamic_cast<CBase*>( base );   CEx* e2 = static_cast<CBase*>( base );   CEx* e3 = reinterpret_cast<CBase*>( base );   CEx* e4 = (CBase*)( base ); }

  • 関数の引数をvoid*でキャストする

    最近見かけたCのプログラムで、関数の引数の型は void* なのですが、その関数を使うときに 引数をvoid*でキャストしていました。 例えば、 func ( (void*) p ); こういうことです。 私の知っている知識では、 void* と 任意の型のポインタは キャストなしに相互に代入可能です。 関数の引数でも、キャストは要らないものだと思っていました。 そうすると、引数を void* でキャストするのは無意味だと思うのですが、・・・ 違うのでしょうか。処理系によるとか。 逆に、関数の引数の型がchar*などで、渡すものが void* のときはどうなのでしょうか。 下のプログラムは、関数byte_orderの引数の型はvoid*ですが、int型へのポインタ( &a )を設定しています。私の環境では、コンパイルエラーも警告もないし、動作も正常です。 #include <stdio.h> #include <string.h> void byte_order(void *vp) { char char_array[4]; strncpy(char_array, vp, 4); printf("出力します:%x %x %x %x\n", char_array[0], char_array[1], char_array[2], char_array[3]); } int main(void) { int a = 0x12345678; byte_order(&a); return 0; } このプログラムは単なる一例であって、質問はバイトオーダに関するものではありません。 また、C言語の質問であって、C++ではありません。

  • 仮想関数と継承について

    #include <iostream> using namespace std; class AAA { public: virtual aaa() { cout <<"aaa(void)"<<endl;} }; class BBB : public AAA { public: virtual aaa(int a) { cout <<"aaa(int)"<<a<<endl;} }; int main() { BBB bbb; bbb.aaa();<--これがエラーになります。 return 0; } どうして、既定クラスの引数なしのaaa()はよぶことが できないのでしょうか?

  • 仮想関数について困っています

    仮想関数について困っています C++を現在勉強中でその中で困ったことができました。 仮想関数についてまだ分かっていないことが多いのですが、一応以下のように使うものだと学びました。 class test{  public:   virtual void run(){    std::cout<<"testクラス"<<std::endl;   } }; class test_sub:public test{  public:   void run(){    std::cout<<"test_subクラス"<<std::endl;   } }; int main(){  test *t_s;  t_s=new test_sub;  t_s->run(); } ※includeは省略させていただきます こうすれば「test_subクラス」と出力されるはずです。 そこで本題なのですが自作のarrayクラスのようなものはテンプレートクラスになっているのですが array<test*> data; data[0].run(); のように使うと「testクラス」と表示されてしまいます。 これを解決する方法は何かないでしょうか? ご存じの方がいましたら教えていただけると助かります。

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

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

このQ&Aのポイント
  • 今年1/11購入したMFCJ998DN用LC3111-4PKの内のマゼンタのインクカートリッジが不具合
  • 挿入したら「インクを検知しない」とのメッセージが表示され、有効期限は2024.08のはずなのに問題発生
  • Windows10で有線LAN・無線LANに接続しており、電話回線はアナログ回線またはISND回線
回答を見る

専門家に質問してみよう