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は無料ではなく、悪用される可能性があります。(率直に言って、よく使用されているのを見るよりもはるかに頻繁に悪用されているのを目にしました。)頻繁に使用していることに気付いた場合は、他のアプローチを検討することをお勧めします。