質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

6回答

10461閲覧

クラスの設計が上手く行かず、dynamic_castを使用してしまう

KureteRubyLua

総合スコア206

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

2グッド

1クリップ

投稿2017/05/31 14:21

以下のような形のソースコードをdynamic_castを使わずに実行したいのですが、上手く行きません。何か方法はないのでしょうか?

C++

1 2class Foo 3{ 4 Foo(); 5 virtual void bar()=0; 6} 7class Foo_A 8{ 9 Foo_A(); 10 virtual void bar(); 11 double baz(); 12} 13 14class Foo_B 15{ 16 Foo_B(); 17 virtual void bar(); 18 int qux(int num); 19} 20 21//関数の実装は省略 22 23int main() 24{ 25 Foo* arr[100]; 26 for(int i=0;i < 100;i++) 27 { 28 //random()は乱数を呼ぶ関数 29 if(random()%2 == 1) 30 { 31 arr[i] = new Foo_A(); 32 } 33 else 34 { 35 arr[i] = new Foo_B(); 36 } 37 } 38 39 Foo_A* foo_a; 40 Foo_B* foo_b 41 42 //この部分でdynamic_castを使用してしまう 43 for(int i=0;i<100;i++) 44 { 45 foo_a=dynamic_cast<Foo_A*>(vec[i]); 46 foo_b=dynamic_cast<Foo_B*>(vec[i]); 47 //random()は乱数を呼ぶ関数 48 if(foo_a!=nullptr) 49 { 50 arr[i].baz(); 51 } 52 else if(foo_b!=nullptr) 53 { 54 arr[i].qux(10); 55 } 56 } 57 //メモリの解放は省略 58} 59

このようにdynamic_castを使って遅いソースコードになってしまいます。dynamic_castやダウンキャストは余り使用したくありません。

本来ならば動的ポリモーフィズム等を駆使してint qux(int num)とdouble baz()を一つの純粋仮想関数にし、派生先で実際の動作を別々に実装するべきなのでしょうが、実際のコードではどうしても戻り値と引数が異なる形になってしまい、上手くまとめる事が出来ません。

このような問題に対してはdynamic_castを使って実装をするしか手はないのでしょうか?それとも何か別の方法があるのでしょうか?よろしくお願いします。

BeatStar, SaitoAtsushi👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答6

0

ベストアンサー

ポリモーフィズムはインターフェイスが共通するものを同じように扱えますが、インターフェイスが異なるという条件では dynamic_cast を使わざるを得ません。 他の方法を考えるにしても、そもそもの設計を見直さないと似たような不恰好さをかかえてしまうと思います。

が、その不恰好さを隠す方法はいくつか考えられます。

たとえば単にマクロとして

#define foo_action(elm, case_a_func, case_b_func) \ if(Foo_A* foo=dynamic_cast<Foo_A*>(elm)) { \ foo->case_a_func; \ } else if(Foo_B* foo=dynamic_cast<Foo_B*>(elm)) { \ foo->case_b_func; \ }

というように定義しておけば、呼出しの箇所では

for(int i=0;i<100;i++) { foo_action(arr[i], baz(), qux(10)); }

という風になるので、少し見通しがよい気がします。 (設計としてアレなのはかわりませんが。)

ただ、質問の例ではメンバ関数の返却値を利用していませんが、実際には利用するわけですよね。 そこらへんをどうしたものかと思うのですが、 Foo_A のとき、 Foo_B のときそれぞれのときにどうするかをラムダ式で与えられるようにすれば自由度の高い表現ができるのではないかと思います。

つまり、

#include <functional> void foo_action(Foo* e, std::function<double(Foo_A*)> a, std::function<int(Foo_B*)> b) { if(Foo_A* foo=dynamic_cast<Foo_A*>(e)) a(foo); else if(Foo_B* foo=dynamic_cast<Foo_B*>(e)) b(foo); }

というような関数を定義しておけば

for(int i=0;i<100;i++) { foo_action(arr[i], [](Foo_A* e){return e->baz();}, [](Foo_B* e){return e->qux(10);}); }

という風に呼出せます。

返却値に対して何か処理したいことがあるのであればラムダ式の中に書けばよいわけです。 マクロよりは (型で制約されるので) マシかなという感じです。

投稿2017/05/31 17:49

SaitoAtsushi

総合スコア5444

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KureteRubyLua

2017/06/02 22:03

結局設計そのものを一から見直しました。 どうも有難うございました。
guest

0

こんにちは。

あまりスマートな方法は思いつきませんでした。

まず、static_cast<>でダウンキャストする方法が考えられます。
virtual is_Foo_A();のような仮想関数を設けて、その結果でstatic_cast<>です。

ダウンキャストしない方法としては、上記のis_Foo_A()に加えて、baz()とqux()の両方とも純粋でない仮想関数にしてFoo_AとFoo_Bは必要な方だけ定義し、必要に応じて呼び分ける感じでしょうか。

どちらにしてもあまりスマートではないですね。ダウンキャストしますが前者の方がまだましかなとは思います。

llvmに含まれるlibToolingdyn_cast<>という関数テンプレートでダウンキャストしてます。
linux上のlibToolingはRTTIを禁止してビルドしてあるのでdynamic_cast<>は使えませんでした。なので、dyn_cast<>の実装にはstatic_cast<>かreinterpret_cast<>が使われている筈です。多重継承に対応するために、恐らくstatic_cast<>だろうと思います。
つまり、llvmのdyn_cast<>は(もっと洗練されてますが)、前者と大差はない実装だろうと思います。

投稿2017/05/31 15:03

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

実際の背景情報までは分かりませんが、掲示コードの範囲だとdynamic_castを使わざるを得ないように思えます。

型(type)による処理分岐を行う場合、C++言語機能としては「仮想関数(virtual function)」か「RTTI+dynamic_cast」が利用できます。関数インタフェース(引数型や戻値型)を揃えることが困難ということであれば、後者のようにRTTIに基づく処理分岐しか解法が無いと思います。


このようにdynamic_castを使って遅いソースコードになってしまいます。dynamic_castやダウンキャストは余り使用したくありません。

本質的にはdynamic_cast相当処理が必要になるかと思いますが、専用実装を行うことで実行時オーバーヘッドを下げることはできます。(一般性を失ったdynamic_castの再実装)

C++

1class Foo_A; 2class Foo_B; 3 4class Foo { 5 virtual Foo_A* asA() { return nullptr; } 6 virtual Foo_B* asB() { return nullptr; } 7}; 8 9class Foo_A : public Foo { 10 double baz(); 11 virtual Foo_A* asA() override { return this; } 12}; 13 14class Foo_B : public Foo { 15 int qux(int num); 16 virtual Foo_B* asB() override { return this; } 17};

C++

1if (Foo_A* foo_a = vec[i].asA()) { 2 foo_a->baz(); 3} else if (Foo_B* foo_b = vec[i].asB()) { 4 foo_b->qux(10); 5}

投稿2017/06/01 01:30

yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

平凡な回答ですが、異なる操作baz,quxを持つFoo_AとFoo_Bがあり、それでもなおそれらをFooとして統一的に扱うような必要があるのであれば、やはりbazとquzはvirtualとして設計してはいけないでしょうか。

Fooにはbaz,quxともにデフォルト実装としてIllegalOperationExceptionを発するようなデフォルト実装をしておき、Foo_Aにはbazのみoverride, Foo_Bにはquxのみoverrideする感じです。

Chironianさんがおっしゃるように呼び出し側でbaz,quxをそのインスタンスに対して呼び出していいかどうかの判定が必要になりますが、判定を間違えてIllegalOperationExceptionが発せられてしまう危険とキャストが実行時に失敗する危険は「同じこと」という考え方です。

Fooの定義を自分の自由にできない場合は無理ですが・・・

投稿2017/05/31 15:20

KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

外からFooのAとBを同じように扱うことを考えよう
サンプルからじゃマジックナンバーである10とかがどういう扱いなのかがわからない

呼び出すだけで良いならhmmmさんのように事前に全てやっておくという方法もあるし
共通の引数の型に一旦ぼかすという方法もある。

呼び場所で識別して引数をどうしても代入しなきゃならないならそもそもこういう形にはあまりしないけど、予備元も呼び出しルールを把握していれば、1個の関数を経由するようにはできるよ。

下記のルールなら、vector<void*>と一緒にFooを特定する方法を書くことによってディスパッチする機能を作ることはできるよ。

enum FooType { FtA, FtB } class Foo { public: virtual FooType GetType(); // vector<void*>型は要件を満たせばなんでも良い // ただ、この型を多機能にすると共通化は簡単になっていく virtual void calc(const vector<void*> &args)= 0; } class Foo_A { public: virtual FooType GetType() override { return FtA; } virtual void calc(const vector<void*> &args) override { // いらない場合無視する baz(); } } class Foo_B { public: virtual FooType GetType() override { return FtB; } virtual void calc(const vector<void*> &args) override { // argsの長さとかをチェックしてキャストしながら突っ込む qux((int)args[0]); } } int main() { Foo* arr[100]; for(int i=0;i < 100;i++) { //random()は乱数を呼ぶ関数 if(random()%2 == 1) { arr[i] = new Foo_A(); } else { arr[i] = new Foo_B(); } } vector<void*> empty; Foo *foo; for(int i=0;i<100;i++) { // vec[i] って arr[i]のこと? foo = arr[i]; // ここでfooを識別しなくてよい方法があればより良い switch(foo->GetType()) { default: foo->calc(empty); break; case FtB: { vector<void*> args; args.push_back(10); foo->calc(args); } break; } } //メモリの解放は省略 }

ちょっと動作確認まではしてないからあれだけどね。

#追記

一応よりよい形はこんな感じかな

C++

1struct Request 2{ 3 FooType Type; 4 vector<void*> Args; 5} 6 7int main() 8{ 9 // Fooのそれぞれの実装を登録する 10 std::map<FooType, Foo*> fooMap; 11 fooMap[FtA] = new Foo_A(); 12 fooMap[FtB] = new Foo_B(); 13 14 // リクエストはどのFooに何の引数を渡すかだけ知っている 15 Request arr[100]; // ※ちょっとこんな風に配列にできたか定かじゃないが… 16 for(int i=0;i < 100;i++) 17 { 18 //random()は乱数を呼ぶ関数 19 if(random()%2 == 1) 20 { 21 arr[i].Type = FtA; 22 } 23 else 24 { 25 arr[i].Type = FtB; 26 arr[i].Args.push_back(10); 27 } 28 } 29 30 // Requestの処理はFooの特定と渡された引数の移譲だけで良い 31 for(int i=0;i<100;i++) 32 { 33 // 好きなFooの実装は FooType と std::vector<void*> の組み合わせがあれば呼び出せる 34 fooMap[arr[i].Type]->calc(arr[i].args); 35 } 36 37 //メモリの解放は省略 38} 39

投稿2017/06/01 03:00

編集2017/06/01 05:49
haru666

総合スコア1591

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

何がしたいのかいまいち分からないんだけど継承使いたいなら以下のような感じにするんじゃないでしょうか。

c++

1class Foo 2{ 3public: 4 Foo(); 5 virutal ~Foo(); 6 virtual void bar()=0; 7 virtual void calc() = 0; 8} 9class Foo_A 10{ 11public: 12 Foo_A(); 13 virutal ~Foo_A(); 14 virtual void bar(); 15 virtual void calc() override{ 16 baz(); 17 } 18 double baz(); 19} 20 21class Foo_B 22{ 23public: 24 Foo_B(); 25 virutal ~Foo_B(); 26 virtual void bar(); 27 virtual void calc() override{ 28 qux(num_); 29 } 30 void set_argument(int num){ 31 num_ = num; 32 } 33 int qux(int num); 34 int num_; 35} 36 37//関数の実装は省略 38 39int main() 40{ 41 Foo* arr[100]; 42 for(int i=0;i < 100;i++) 43 { 44 //random()は乱数を呼ぶ関数 45 if(random()%2 == 1) 46 { 47 arr[i] = new Foo_A(); 48 } 49 else 50 { 51 auto b = new Foo_B(); 52 b->set_argument(10); 53 arr[i] = b; 54 55 } 56 } 57 58 for(int i=0;i<100;i++) 59 { 60 vec[i]->calc(); 61 } 62 //メモリの解放は省略 63} 64

投稿2017/06/01 01:29

hmmm

総合スコア818

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問