• 締切済み

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

環境は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