C++ 基底クラスから派生クラスのポインタをキャストせずに取得する方法を教えてください
解決済
回答 2
投稿
- 評価
- クリップ 0
- VIEW 4,219
前提・実現したいこと
C++でゲームプログラムを組んでいます。UpdateやDrawといった機能をComponentの派生クラスとして作り、それをゲームに登場するキャラクタークラスにComponentのポインタとして(ポリモフィズムの為)メンバに持たせる形で実装したいです。さらに、キャストを使わずに実現したいです。
そこで下記のようなコードを書いたのですが、エラーが出てしまい、解決できずに今に至ります。
ご教授願います。
発生している問題・エラーメッセージ
型 "Component *" の値を使用して型 "Draw *" のエンティティを初期化することはできません
該当のソースコード
#include <iostream>
#include <unordered_map>
using namespace std;
//------------------------------------
// 様々なコンポーネントの基底クラス
// virtual get()関数は派生クラスでthisを返すようにし、例えば
// Component* co = new Draw();
// Draw* draw = co->get()で派生クラスを取得するつもりだったが、上手くいかず……
//------------------------------------
class Component {
public:
virtual void update() = 0;
virtual Component* get() {
cout << "Component get" << endl;
return this;
}
};
//------------------------------------
// Updateコンポーネント
//------------------------------------
class Update : public Component {
public:
void update() {
cout << "Update update" << endl;
}
Update* get() {
cout << "Update get" << endl;
return this;
}
};
//------------------------------------
// Drawコンポーネント
//------------------------------------s
class Draw : public Component {
public:
void update() {
cout << "Draw update" << endl;
}
virtual Draw* get() {
cout << "Draw get" << endl;
return this;
}
};
//------------------------------------
// ゲームに登場するクラスの基底クラス
//------------------------------------
class GameObject {
public:
// コンポーネントリストから指定したコンポーネントを返す
Component* component( string str ) {
return mComponentList.at( str );
}
// コンポーネントを登録する
void regComponet( string key, Component* co ) {
mComponentList.emplace( key, co );
}
// コンポーネントリスト
unordered_map<string, Component*> mComponentList;
};
//------------------------------------
// メイン
//------------------------------------
int main() {
// コンポーネントの生成
Update* update = new Update();
Draw* draw = new Draw();
// ゲームオブジェクトの生成
GameObject* gameObject = new GameObject();
// ゲームオブジェクトにコンポーネントを登録する
gameObject->regComponet( "update", update );
gameObject->regComponet( "draw", draw );
// キャストすれば取得できるが……
// 呼ばれているのはDraw::get()だが、戻り値はComponent*
// クラスの中でのthisは派生クラスではないのか…… ?
Draw* d = (Draw*)gameObject->component( "draw" )->get();
d->update();
}
補足情報(FW/ツールのバージョンなど)
VisualStudio 2017
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+1
GameObject::component( string str )
の戻り値がComponent*
型で、Component::get()
の戻り値もComponent*
型なので、基底クラスから'get()'をコールすれば当然Component*
型のオブジェクトが取れるだけです。
ポリモーフィズムはあくまで「共通のインターフェース、複数の実装」のために機構だと思いますので、インスタンスによって戻り値の型が変わるというのは共通のインターフェースではなくなってしまうのでそもそも考え方として破綻していると思います。
updateを呼び出したいだけでしたら
gameObject->component( "draw" )->update();
とでもすればOKです。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+1
こんにちは。
派生クラスの仮想関数get()が呼ばれていて、それが派生クラスへのポインタを返しているのに、実際には基底クラスへのポインタが返ってくるのは何故という疑問ですね?
C++の型情報はコンパイラが処理します。そのため、実行時には型情報が事実上失われています。
そして、動的ポリモーフィズムは、実行時でないと型が分からないオブジェクトを同じ記述で安全に取り扱える非常に優れた仕組みです。
Draw* d = (Draw*)gameObject->component( "draw" )->get()
の部分を、例えばDraw* d = (Draw*)gameObject->component( argv[1] )->get()
と記述し、コマンドライン・パラメータで "draw" を指定できます。
しかし、もしかすると、使う人は "update" を指定するかも知れません。この場合、gameObject->component( argv[1] )->get()
は、Update クラスへのポインタを返却します。・・・①
さて、コンパイラはコマンド・ライン・パラメータとして、"draw"が指定されるのか"update"が指定されるのか知るよしもありません。なので、gameObject->component( argv[1] )->get()
が返却する型をコンパイラは知ることができません。
ところで、基底クラスにも get() を定義すると決まっており、これはComponentクラスへのポインタを返却すると指定されています。そこで、コンパイラはgameObject->component( argv[1] )->get()
が返却するものはComponentクラスへのポインタであるとして取り扱います。(そのように標準規格にて決められています。)
従って、コンパイラはDraw* d = gameObject->component( "draw" )->get()
を、「基底クラスへのポインタを暗黙的に派生クラスへのポインタへ変換する指示」として解釈しエラーにします。これは①が起きた時にDraw型へのポインタd
にUpdate型へのポインタ
の代入を事前に回避するための機能です。この機能がなかった場合、気が付かない内にUpdate型へのポインタをDraw型のポインタへ代入し、Update型のオブジェクトをDraw型と思い込んで処理するので破綻します。そして、デバッグで地獄を見ることになるのです。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.10%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2018/07/10 10:28
GameObject::component( string str )の戻り値がComponent*型ですが、get関数は仮想関数である為、呼ばれるのは派生クラスのgetです。派生クラスのgetは戻り値は派生クラスの型のポインタ型であり、thisを返します。実際に実行して呼ばれていることを確認したのですが、何故Component*型が返ってきてしまうのでしょうか?
2018/07/10 10:46
2018/07/10 10:55