簡體   English   中英

如何在C ++中向下轉換以從父實例調用子函數?

[英]How to downcast in C++ to call a child function from a parent instance?

我試圖使用顯式向下轉換從父實例調用子函數(感謝指出@Aconcagua)。 作為C ++的初學者,我有這樣的事情:

Road currentRoad = ...;
duration = ((SpeedDataRoad) currentRoad).getSpeedProfileTime(dateinMillis, isRightDirection);

SpeedDataRoad繼承自Road

class SpeedDataRoad : public Road{ 
     double getSpeedProfileTime(long dateinMillis, bool isRightDirection) {

     ...

}

但是我收到錯誤:

從'Road'到'SpeedDataRoad'的C風格演員陣容沒有匹配的轉換

任何關於我做錯的建議都將不勝感激。


為了清楚起見,我在Java中嘗試實現的內容將像這樣編寫並正常工作:

duration = ((SpeedDataRoad) currentRoad).getSpeedProfileTime(currentTime, isRightDirection);

您遭受稱為“對象切片”的效果:

SpeedDataRoad sdr;
Road currentRoad = sdr;

在第二行, sdr 按值分配給currentRoad ,但后者不適合保存完整的SpeedDataRoad對象。 因此,所有剩余的SpeedDataRoad都被簡單地切除,剩下的只是一個純粹的Road對象,只包含原始sdr對象的Road部分。

同時,由於您只剩下一個純Road對象,因此無法將其SpeedDataRoadSpeedDataRoad對象。 現在缺少的部分應該從哪里來?

這與您不能將多態類型直接放入基類的容器(如std::vector )中的原因完全相同。

你需要的是指針(如果你想能夠重新分配)或引用(否則是首選):

SpeedDataRoad sdr;
Road& currentRoad = sdr;
//  ^ (!)
// or:
Road* currentRoad = &sdr;

現在你可以做演員了。 但明確的下線有一種糟糕的設計氣味。 從一開始就使用多態方法可能會更好:

class Road
{
public:
    virtual double getSpeedProfileTime(long, bool) = 0;
    //                                             ^ pure virtual
    // alternatively, you can provide a default implementation
};

class SpeedDataRoad : public Road
{
public:
    double getSpeedProfileTime(long, bool) override
    { /* ... */ }
};

現在你可以簡單地:

SpeedDataRoad sdr;
Road& currentRoad = sdr;
double profile = currentRoad.getSpeedProfileTime(0, false);

作為虛擬,你總是得到函數的正確變體,無論我們擁有哪個子類,以及它可能覆蓋函數的方式......

旁注1:您可能更喜歡更現代的C ++演員表,而不是舊的C風格演員陣容,您可以控制更精細的實際操作:

Road* someRoad = ...;
SpeedDataRoad* sdr = static_cast<SpeedDataRoad*>(someRoad);
SpeedDataRoad* sdr = dynamic_cast<SpeedDataRoad*>(someRoad);

如果您100%確定對象只能是所需類型,則可以使用static_cast 在這種情況下,你可以避免任何根本不能提供任何服務的運行時測試(你還是100%肯定,還記得嗎?)。 奇怪的重復模板模式是一個典型的場景。

如果你不能確定類型,那么dynamic_cast就會起作用,它會做一些運行時類型檢查,只返回一個空指針(如果在指針上使用)或者拋出一個std::bad_cast (如果用在引用上),如果實際類型不是所需類型(或子類)。 當不同的多態類型存儲在向量中時(作為基類的指針,見上文),可能會出現這種情況。 但是再說一遍:需要一個演員可能暗示你的設計存在缺陷......

(為了完整性:還有const_castreinterpret_cast ,但你應該遠離這些,除非你真的,真的知道你做了什么。)

附注2:與Java的差異。

在Java中,我們在本機和引用類型之間隱式區分。 原生的總是通過值傳遞,引用類型總是通過引用傳遞 - 好吧, Java引用 ,它實際上比C ++引用更像是一個C ++ 指針 (可以為null ,可以重新賦值)。 在Java中,這是隱式發生的,在C ++中,您需要明確(另一方面,您可以為任何類型都有兩種行為)。

Java強制轉換(Java!)引用的行為類似於C ++ dynamic_cast (在引用時,即拋出,它在類型不匹配時不會返回null )。

最后(關於我的多態性建議),在Java中,所有函數都是隱式虛擬的,在C ++中,你必須再次明確(應用virtual關鍵字,見上文)。

您正在切割 SpeedDataRoad對象。 與具有指針/引用語義的Java對象不同,C ++對象具有值語義。 這意味着,在您的示例中, currentRoadRoad ,而不是SpeedDataRoad 它是您在...創建SppedDataRoadRoad部分的副本

要使用多態性是C ++,您需要使用引用或指針。 也就是說,以下操作無效,因為currentRoad 不是 SpeedDataRoad

double foo(Road currentRoad)
{
    //...
    return ((SpeedDataRoad)currentRoad).getSpeedProfileTime(currentTime, isRightDirection);
}

int main()
{
    SpeedDataRoad road;
    foo(road);
}

雖然以下內容可行,因為currentRoad 引用SpeedDataRoad

double foo(Road& currentRoad)
//             ^---------------- Pass by reference now
{
    //...
    return dynamic_cast<SpeedDataRoad&>(currentRoad).getSpeedProfileTime(currentTime, isRightDirection);
    //     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //     Using C++-style cast and casting reference to reference
}

int main()
{
    SpeedDataRoad road;
    foo(road);
}

在第一個示例中, currentRoadRoad road副本 ,而在第二個示例中, currentRoad是對road引用


您還應該避免在C ++中使用C風格的強制轉換。 在這種情況下,最好使用dynamic_cast<SpeedDataRoad&>(currentRoad)或者,如果您絕對確定currentRoad將始終是對SpeedDataRoad對象的引用, SpeedDataRoad static_cast<SpeedDataRoad&>(currentRoad) 如果currentRoad不是對SpeedDataRoad的引用,前者將執行運行時類型檢查並拋出異常,而后者將避免執行運行時類型檢查的(小)開銷,但如果currentRoad不是引用,則會導致未定義的行為到SpeedDataRoad

這被稱為向下傾斜而不是向上 - 通過dynamic_cast直截了當地實現這一目標:

if (SpeedDataRoad* sdroad = dynamic_cast<SpeedDataRoad*>(&currentRoad); sdroad != nullptr) {
   duration = sdroad->getSpeedProfileTime(currentTime, isRightDirection);
}

如果要在函數內部檢查是否可以將指針/引用向下轉換為子項,則需要使用動態強制轉換。

void foo(Road* road){
  SpeedDataRoad* child{nullptr};
  if(child = dynamic_cast<SpeedDaraRoad*>(road){
    // Do something with SpeedDataRoad
  } else {
     // road is not an instance of SpeedDataRoad
  }
}

你也可以使用帶引用的dynamic_cast ,就像這樣

cppSpeedDataRoad& child = dynamic_cast<SpeedDataRoad&>(reference_to_road);

但要小心,好像演員表失敗, std::bad_cast將被拋出。

在C ++中,我們盡量不使用C風格的轉換。 (typename)object 相反,有4種類型的類型轉換。

  1. static_cast<typename*>(pointer) static_cast<typename>(value) :指針的上傳和值類型的類型轉換

  2. dynamic_cast<typename*>(pointer)dynamic_cast<typename*>(pointer)安全向下轉換(你應該使用的那個)。 它對upcasting進行運行時檢查,因此存在運行時成本。

  3. const_cast<...>(...) :常量類型

  4. reinterpret_cast<typename*>(pointer) reinterpret_cast<typename>(value) :類似於C風格的強制轉換。

對於對象(已分配堆棧),我很少使用強制轉換。 因為C ++中的自然對象有自己的大小,所以轉換可能會改變它們的大小。

暫無
暫無

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

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