dynamic_cast
オペレーターについて簡単な質問があります。これが実行時の型識別、つまり実行時のオブジェクト型を知るために使用されることは知っています。しかし、あなたのプログラミング経験から、この演算子を使用しなければならなかった実際のシナリオを教えてください。それを使わずに苦労したことは何ですか?
9 に答える
おもちゃの例
ノアの箱舟は、さまざまな種類の動物の入れ物として機能します。箱舟自体はサル、ペンギン、蚊の違いを気にしないので、クラスを定義し、そこからクラスをAnimal
派生させ、それぞれを箱舟に格納します。Monkey
Penguin
Mosquito
Animal
洪水が終わったら、ノアは動物を地球全体の彼らが属する場所に分配したいと思っているので、彼の箱舟に保管されている一般的な動物についての追加の知識が必要です。一例として、彼は今、どの動物が南極で放出されるペンギンであり、どれがそうでないかを理解するためにdynamic_cast<>
、各動物を試してみることができます。Penguin
実例
イベント監視フレームワークを実装しました。このフレームワークでは、アプリケーションが実行時に生成されたイベントをリストに格納します。イベントモニターはこのリストを調べて、関心のある特定のイベントを調べます。イベントタイプは、、、、などのOSレベルのものSYSCALL
でしFUNCTIONCALL
たINTERRUPT
。
ここでは、すべての特定のイベントをEvent
インスタンスの一般的なリストに保存しました。dynamic_cast<>
次に、モニターはこのリストと、関心のあるタイプに対して見たイベントを繰り返し処理します。他のすべてのイベント(例外を発生させるもの)は無視されます。
質問:イベントタイプごとに個別のリストを作成できないのはなぜですか?
回答:これは可能ですが、チェックするそれぞれのリストを全員が知っている必要があるため、新しいイベントと新しいモニター(複数のイベントタイプを集約)でシステムを拡張するのが難しくなります。
典型的なユースケースは訪問者パターンです:
struct Element
{
virtual ~Element() { }
void accept(Visitor & v)
{
v.visit(this);
}
};
struct Visitor
{
virtual void visit(Element * e) = 0;
virtual ~Visitor() { }
};
struct RedElement : Element { };
struct BlueElement : Element { };
struct FifthElement : Element { };
struct MyVisitor : Visitor
{
virtual void visit(Element * e)
{
if (RedElement * p = dynamic_cast<RedElement*>(e))
{
// do things specific to Red
}
else if (BlueElement * p = dynamic_cast<BlueElement*>(e))
{
// do things specific to Blue
}
else
{
// error: visitor doesn't know what to do with this element
}
}
};
今、あなたがいくつか持っているならElement & e;
、あなたは作っMyVisitor v;
て言うことができますe.accept(v)
.
重要な設計機能は、Element
階層を変更する場合、訪問者を編集するだけでよいことです。このパターンは依然としてかなり複雑であり、s のクラス階層が非常に安定している場合にのみ推奨されますElement
。
次の状況を想像してください。HTML を読み取って表示する C++ プログラムがあるとします。HTMLElement
純粋仮想メソッドを持つ基本クラスがありdisplayOnScreen
ます。renderHTMLToBitmap
HTML をビットマップに描画するという関数もあります。それぞれHTMLElement
に がある場合は、要素を表すvector<HTMLElement*> children;
を渡すだけです。しかし、CSS を追加するなど、いくつかのサブクラスに特別な処理が必要な場合はどうでしょうか。CSS関数に渡すことができるように、要素が であるかどうかを知る方法が必要です。それを見つけるには、を使用します。HTMLElement
<html>
<link>
LinkElement
dynamic_cast
dynamic_cast
一般にポリモーフィズムの問題は、あまり効率的ではないということです。ミックスに vtables を追加すると、さらに悪化します。
基本クラスに仮想関数を追加すると、それらが呼び出されるときに、実際には関数ポインターとメモリ領域のかなりの数のレイヤーを通過することになります。call
これは、ASM命令のようなものよりも効率的ではありません。
編集: 以下のアンドリューのコメントに応えて、ここに新しいアプローチがあります: 特定の要素タイプ ( ) への動的キャストの代わりに、代わりに、何も表示しない関数でオーバーライドし、新しい純粋仮想関数を作成する、呼び出されたLinkElement
別の抽象サブクラスがあります。 : . はtest に変更され、 を呼び出すだけです。仮想メソッドを使用すると、同じ種類のサブクラスができます。HTMLElement
ActionElement
displayOnScreen
virtual void doAction() const = 0
dynamic_cast
ActionElement
doAction()
GraphicalElement
displayOnScreen()
編集 2: 「レンダリング」メソッドは次のようになります。
void render(HTMLElement root) {
for(vector<HTLMElement*>::iterator i = root.children.begin(); i != root.children.end(); i++) {
if(dynamic_cast<ActionElement*>(*i) != NULL) //Is an ActionElement
{
ActionElement* ae = dynamic_cast<ActionElement*>(*i);
ae->doAction();
render(ae);
}
else if(dynamic_cast<GraphicalElement*>(*i) != NULL) //Is a GraphicalElement
{
GraphicalElement* ge = dynamic_cast<GraphicalElement*>(*i);
ge->displayToScreen();
render(ge);
}
else
{
//Error
}
}
}
オペレーターdynamic_cast
は、動的ディスパッチ (仮想関数、ビジター パターンなど) と同じ問題を解決します。オブジェクトのランタイム タイプに基づいてさまざまなアクションを実行できます。
ただし、おそらく必要な数が増えない場合を除いて、常に動的ディスパッチを優先するdynamic_cast
必要があります。
例えば。あなたは決してすべきではありません:
if (auto v = dynamic_cast<Dog*>(animal)) { ... }
else if (auto v = dynamic_cast<Cat*>(animal)) { ... }
...
保守性とパフォーマンス上の理由からですが、たとえば次のことができます。
for (MenuItem* item: items)
{
if (auto submenu = dynamic_cast<Submenu*>(item))
{
auto items = submenu->items();
draw(context, items, position); // Recursion
...
}
else
{
item->draw_icon();
item->setup_accelerator();
...
}
}
これは、まさにこの状況で非常に役立つことがわかりました。個別に処理する必要がある非常に特定の下位階層が 1 つあります。これがdynamic_cast
光る場所です。しかし、実際の例は非常にまれです (メニューの例は私が対処しなければならなかったものです)。
dynamic_cast は、仮想関数の代替として意図されたものではありません。
クラス階層全体をウォークスルーする必要があるため、dynamic_cast には自明ではないパフォーマンス オーバーヘッドがあります (またはそう思います)。
dynamic_cast は、C# の「is」演算子や古き良き COM の QueryInterface に似ています。
これまでのところ、dynamic_cast の実際の
使用法を 1 つ見つけました:
(*)複数の継承があり、キャストのターゲットを見つけるために、コンパイラはクラス階層を上下に移動してターゲットを見つける必要があります (または、必要に応じて上下に移動します)。 . これは、キャストのターゲットが、キャストのソースが階層内のどこにあるかに関連して、並列ブランチにあることを意味します。このようなキャストを行う方法は他にないと思います。
他のすべてのケースでは、基本クラス virtual を使用して、所有しているオブジェクトのタイプを通知し、その場合にのみそれをターゲット クラスに dynamic_cast して、非仮想機能の一部を使用できるようにします。理想的には、非仮想機能は存在しないはずですが、一体、私たちは現実世界に住んでいます。
次のようなことを行います:
if (v = dynamic_cast(...)){} else if (v = dynamic_cast(...)){} else if ...
パフォーマンスの無駄です。
コントラクトプログラミングとRTTIdynamic_cast
は、オブジェクトが実装するインターフェイスをアドバタイズできるようにするために使用できる方法を示しています。私の店では、かなり不透明なメタオブジェクトシステムを置き換えるためにそれを使用しました。これで、プラットフォームが「ベイク」されてから数週間/月後に新しいモジュールによってオブジェクトが導入された場合でも、オブジェクトの機能を明確に説明できます(もちろん、契約は事前に決定されている必要があります)。
作業しているエンティティの型がわかっているコードを書いているほとんどの状況では、より効率的な static_cast を使用するだけです。
動的キャストが必要な状況は、通常、(私の経験では) 設計の先見の明の欠如から発生します。通常、設計者は、コードの後半で型を決定できる列挙型または ID を提供できません。
たとえば、私はすでに複数のプロジェクトでこの状況を見てきました。
ユーザーが明示的に選択するのではなく、ユーザーが必要とする派生クラスを内部ロジックが決定するファクトリを使用できます。そのファクトリは、完全な世界では、返されたオブジェクトのタイプを識別するのに役立つ列挙を返しますが、そうでない場合は、どのタイプのオブジェクトが与えられたかを dynamic_cast でテストする必要があるかもしれません。
あなたのフォローアップの質問は明らかに次のようになります: ファクトリを使用するコードで使用しているオブジェクトの型を知る必要があるのはなぜですか?
完璧な世界では、そうはなりません。基本クラスによって提供されるインターフェイスは、ファクトリから返されたすべてのオブジェクトを必要なすべてのエクステントで管理するのに十分です。しかし、人々は完璧に設計することはできません。たとえば、ファクトリが抽象接続オブジェクトを作成する場合、ソケット接続オブジェクトの UseSSL フラグにアクセスする必要があることに突然気付くかもしれませんが、ファクトリ ベースはそれをサポートしておらず、インターフェース。そのため、ロジックでそのタイプの派生クラスを使用しているかどうかを確認し、使用している場合はフラグを直接キャスト/設定します。
醜いですが、完璧な世界ではありません。仕事のプレッシャーの下で、現実の世界で不完全な設計を完全にリファクタリングする時間がない場合があります。
キャストは、基本的にコンパイラーに対して、あなたがよく知っていることを伝えており、通常は設計上の決定が弱いことを示しているため、可能な限り避ける必要があります。
ただし、抽象化レベルが 1 つまたは 2 つのサブクラスに対して少し高すぎる状況が発生する可能性があります。この場合、デザインを変更するか、サブクラスを dynamic_cast でチェックして別のブランチで処理することで解決するかを選択できます。トレードオフは、余分な時間を追加することと、後で余分なメンテナンスの問題に対するリスクとの間です。
dynamic_cast オペレーターは私にとって非常に便利です。私は特に、イベント管理のObserver パターンで使用します。
#include <vector>
#include <iostream>
using namespace std;
class Subject; class Observer; class Event;
class Event { public: virtual ~Event() {}; };
class Observer { public: virtual void onEvent(Subject& s, const Event& e) = 0; };
class Subject {
private:
vector<Observer*> m_obs;
public:
void attach(Observer& obs) { m_obs.push_back(& obs); }
public:
void notifyEvent(const Event& evt) {
for (vector<Observer*>::iterator it = m_obs.begin(); it != m_obs.end(); it++) {
if (Observer* const obs = *it) {
obs->onEvent(*this, evt);
}
}
}
};
// Define a model with events that contain data.
class MyModel : public Subject {
public:
class Evt1 : public Event { public: int a; string s; };
class Evt2 : public Event { public: float f; };
};
// Define a first service that processes both events with their data.
class MyService1 : public Observer {
public:
virtual void onEvent(Subject& s, const Event& e) {
if (const MyModel::Evt1* const e1 = dynamic_cast<const MyModel::Evt1*>(& e)) {
cout << "Service1 - event Evt1 received: a = " << e1->a << ", s = " << e1->s << endl;
}
if (const MyModel::Evt2* const e2 = dynamic_cast<const MyModel::Evt2*>(& e)) {
cout << "Service1 - event Evt2 received: f = " << e2->f << endl;
}
}
};
// Define a second service that only deals with the second event.
class MyService2 : public Observer {
public:
virtual void onEvent(Subject& s, const Event& e) {
// Nothing to do with Evt1 in Service2
if (const MyModel::Evt2* const e2 = dynamic_cast<const MyModel::Evt2*>(& e)) {
cout << "Service2 - event Evt2 received: f = " << e2->f << endl;
}
}
};
int main(void) {
MyModel m; MyService1 s1; MyService2 s2;
m.attach(s1); m.attach(s2);
MyModel::Evt1 e1; e1.a = 2; e1.s = "two"; m.notifyEvent(e1);
MyModel::Evt2 e2; e2.f = .2f; m.notifyEvent(e2);
}