簡體   English   中英

dynamic_cast的正確用例是什么?

[英]What is the proper use case for dynamic_cast?

我多次被告知(並且在實踐中看到自己)使用dynamic_cast通常意味着糟糕的設計,因為它可以而且應該被虛函數替換。

例如,請考慮以下代碼:

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
}

可以很容易地看出,我們只需要向Base添加一個虛函數doStuff() ,然后在Derived重新實現它,而不是編寫動態強制轉換。

在這種情況下,我的問題是,為什么我們在語言中都有dynamic_cast? 有沒有一個例子,使用dynamic_cast是合理的?

虛函數的問題在於層次結構中的所有 必須具有實現或是抽象的,並且這絕對不是正確的事情。 例如,如果Base是一個接口,那么在if中,您需要訪問Derived的內部實現細節嗎? 這在虛擬功能中肯定是行不通的。 此外,在某些多重繼承情況下,需要使用dynamic_cast進行向上轉換和向下轉換。 在虛擬功能中可以做些什么是有限制的 - 例如,模板。 最后,有時您需要存儲Derived* ,而不僅僅是調用函數。

實質上,虛函數僅在某些情況下有效,而不是全部

我認為有兩種情況使用dynamic_cast是有效的事情。 第一個是檢查對象是否支持接口,第二個是打破封裝。 讓我詳細解釋一下。

檢查接口

考慮以下功能:

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

    if( transaction )
        transaction->Begin();

    obj->DoStuff();

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

(ITransactionControl將是一個純抽象類。)在此函數中,如果對象支持事務語義,我們希望在事務上下文中使用“DoStuff”。 如果沒有,那么無論如何都可以繼續。

現在我們當然可以向Object類添加虛擬的Begin()和Commit()方法,但是隨后派生自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;
   }
}

再一次,我們可以向IIterator類添加一個虛擬的“CopyElements”方法,但這與我上面提到的相同。 也就是說,它使界面膨脹。 它強制所有實現者都有一個CopyElements方法,即使ArrayIterator是唯一會在其中做一些有趣事情的類。

話雖如此,我建議謹慎使用這些技術。 dynamic_cast不是免費的,可以濫用。 (坦率地說,我看到它的使用頻率遠遠超過我看到它使用得很好。)如果你發現自己經常使用它,那么考慮其他方法是個好主意。

可以很容易地看出,我們只需要向Base添加一個虛函數doStuff(),然后在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;

您是否注意到virtual功能如何消除dynamic_cast

使用dynamic_cast通常表示您無法使用通用接口(即函數)實現目標,因此您需要將其強制轉換為精確類型,以便調用類型基類/派生類的特定成員函數。

子類可能具有基類中不存在的其他方法,並且在其他子類的上下文中可能沒有意義。 但一般來說你應該避免它。

如果你有一個接收BaseClass *的方法(稱之為foo),並且它已經用於DerivedClass *,那該怎么辦? 如果我寫的話:

BaseClass* x = new DerivedClass();

並用x調用foo,我將得到foo(BaseClass varName),而不是foo(DerivedClass varName)。

一種解決方案是使用dynamic_cast並針對NULL測試它,如果它不為null,則使用casted var而不是x調用foo。

它不是最面向對象的情況,但它發生了,而dynamic_cast可以幫助你(好吧,一般來說,投射不是太面向對象)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM