19

I have been told many times (and seen myself in practice) that the use of dynamic_cast often means bad design, because it can and should be replaced with virtual functions.

For example, consider the following code:

class Base{...};
class Derived:public Base{...};
...
Base* createSomeObject(); // Might create a Derived object
...
Base* obj = createSomeObject();
if(dynamic_cast<Derived*>(obj)){
 // do stuff in one way
}
else{
// do stuff in some other way
}

It can be easily seen that instead of writing dynamic casts we can just add a virtual function doStuff() to Base and re-implement it in Derived.

In that case, my question is, why do we have dynamic_cast in the language at all? Is there an example in which the use of dynamic_cast is justified?

4

5 に答える 5

30

仮想関数の問題は、階層内のすべてのクラスが実装を持っているか、抽象化されている必要があることです。これは常に正しいことではありません。たとえば、Baseがインターフェイスで、if の内部実装の詳細にアクセスする必要がある場合はどうなりますDerivedか? それは確かに仮想関数では実行できません。さらに、dynamic_cast特定の多重継承状況でのアップキャストとダウンキャストの両方に必要です。また、テンプレートなど、仮想機能で実行できることには制限があります。Derived*最後に、関数を呼び出すだけでなく、を保存する必要がある場合もあります。

基本的に、仮想機能はすべてではなく、一部のケースでのみ機能します。

于 2011-04-25T09:06:13.717 に答える
20

dynamic_castを使用することが有効な場合は2つあると思います。1つ目は、オブジェクトがインターフェイスをサポートしているかどうかを確認することであり、2つ目は、カプセル化を解除することです。両方について詳しく説明します。

インターフェイスの確認

次の関数について考えてみます。

void DoStuffToObject( Object * obj )
{
    ITransactionControl * transaction = dynamic_cast<ITransactionControl>( obj );

    if( transaction )
        transaction->Begin();

    obj->DoStuff();

    if( transaction )
        transaction->Commit();
}

(ITransactionControlは純粋な抽象クラスになります。)この関数では、オブジェクトがトランザクションセマンティクスをサポートしている場合、トランザクションのコンテキストで「DoStuff」を実行します。そうでない場合は、とにかく先に進んでも問題ありません。

これで、仮想のBegin()メソッドとCommit()メソッドをObjectクラスに追加できますが、Objectから派生するすべてのクラスは、トランザクションを認識していなくても、Begin()メソッドとCommit()メソッドを取得します。この場合、基本クラスで仮想メソッドを使用すると、そのインターフェースが汚染されるだけです。上記の例は、単一責任の原則とインターフェース分離の原則の両方へのより良い順守を促進します。

カプセル化を破る

dynamic_castはカプセル化を解除できるため、一般的に有害であると考えられていることを考えると、これは奇妙なアドバイスのように思えるかもしれません。ただし、正しく実行すれば、これは完全に安全で強力な手法になります。次の関数について考えてみます。

std::vector<int> CopyElements( IIterator * iterator )
{
   std::vector<int> result;

   while( iterator->MoveNext() )
       result.push_back( iterator->GetCurrent() );

   return result;
}

ここには何も問題はありません。しかし、ここで、現場でパフォーマンスの問題が発生し始めたとします。分析した後、プログラムがこの関数内で非常に多くの時間を費やしていることがわかります。push_backsにより、複数のメモリが割り当てられます。さらに悪いことに、「イテレータ」はほとんどの場合「ArrayIterator」であることがわかります。あなただけがその仮定をすることができたなら、あなたのパフォーマンスの問題は消えるでしょう。dynamic_castを使用すると、まさにそれを行うことができます。

 std::vector<int> CopyElements( IIterator * iterator )
{
   ArrayIterator * arrayIterator = dynamic_cast<ArrayIterator *>( iterator );

   if( arrayIterator ) {
       return std::vector<int>( arrayIterator->Begin(), arrayIterator->End() );
   } else {
       std::vector<int> result;

       while( iterator->MoveNext() )
           result.push_back( iterator->GetCurrent() );

       return result;
   }
}

ここでも、仮想の「CopyElements」メソッドをIIteratorクラスに追加できますが、これには上記と同じ欠点があります。つまり、インターフェイスが肥大化します。ArrayIteratorが興味深いことを行う唯一のクラスである場合でも、すべての実装者にCopyElementsメソッドを強制します。

そうは言っても、これらのテクニックは控えめに使用することをお勧めします。dynamic_castは無料ではなく、悪用される可能性があります。(率直に言って、よく使用されているのを見るよりもはるかに頻繁に悪用されているのを目にしました。)頻繁に使用していることに気付いた場合は、他のアプローチを検討することをお勧めします。

于 2012-11-28T17:09:34.980 に答える
7

動的キャストを記述する代わりに、仮想関数 doStuff() を Base に追加し、それを Derived に再実装できることが簡単にわかります。

はい。それがvirtual関数の目的です。

class Base
{
  public:
      virtual void doStuff();
};
class Derived: public Base
{
  public:
      virtual void doStuff(); //override base implementation
};

Base* createSomeObject(); // Might create a Derived object

Base* obj = createSomeObject();
obj->doStuff(); //might call Base::doStuff() or Derived::doStuff(), depending on the dynamic type of obj;

virtualfunction がどのように を排除するかに気付きましたかdynamic_cast?

の使用は通常、共通のインターフェイス (つまり、仮想dynamic_cast関数)を使用して目標を達成できないことを示します。そのため、型の基本/派生クラスの特定のメンバー関数を呼び出すために、正確な型にキャストする必要があります。

于 2011-04-25T08:58:33.147 に答える
0

サブクラスには、基本クラスに存在しない他のメソッドが含まれる場合があり、他のサブクラスのコンテキストでは意味をなさない場合があります。しかし、一般的には避けるべきです。

于 2011-04-25T08:57:42.150 に答える
0

BaseClass* を受け取るメソッド (foo と呼ぶ) があり、それが DerivedClass* に費やされたとします。私が書くなら:

BaseClass* x = new DerivedClass();

x で foo を呼び出すと、foo (DerivedClass varName) ではなく、foo (BaseClass varName) に到達します。

解決策の 1 つは、dynamic_cast を使用して NULL に対してテストし、null でない場合は、x ではなくキャストされた var で foo を呼び出すことです。

これは最もオブジェクト指向の状況ではありませんが、起こります。dynamic_cast が役に立ちます (まあ、キャストは一般的にあまりオブジェクト指向ではありません)。

于 2011-04-25T09:24:56.770 に答える