簡體   English   中英

未在子類中調用C ++虛擬函數

[英]C++ virtual function not called in subclass

考慮以下簡單情況:

A.h

class A {
public:
    virtual void a() = 0;
};

B.h

#include <iostream>

class B {
public:
    virtual void b() {std::cout << "b()." << std::endl;};
};

C.h

#include "A.h"
#include "B.h"

class C : public B, public A {
public:
    void a() {std::cout << "a() in C." << std::endl;};
};

int main() {
    B* b = new C();
    ((A*) b)->a();    // Output: b().

    A* a = new C();
    a->a();           // Output:: a() in C.

   return 0;
}

換一種說法:
-A是純虛擬類。
-B是沒有超類和一個非純虛函數的類。
-C是A和B的子類,並且覆蓋A的純虛函數。

令我驚訝的是第一輸出

((A*) b)->a();    // Output: b().

盡管我在代碼中調用了a(),但b()被調用了 我的猜測是,它與以下事實有關:變量b是指向類B的指針,而不是類A的子類。但是運行時類型仍然是指向C實例的指針。

從Java的角度來看,奇怪的行為是什么確切的C ++規則來解釋的?

您將無條件地使用C樣式強制轉換將b強制轉換為A* 編譯器不會阻止您執行此操作; 您說的是A*所以它是A* 因此,它將指向的內存視為A的實例。 由於a()A的vtable中列出的第一個方法, b()B的vtable中列出的第一個方法,所以當您對確實是B的對象調用a()時,將得到b()

您很幸運,對象布局相似。 不能保證確實如此。

首先,您不應該使用C樣式轉換。 您應該使用安全性更高的C ++強制轉換運算符 (盡管您仍然可以用腳射擊,因此請仔細閱讀文檔)。

其次,除非您使用dynamic_cast<> ,否則您不應依賴這種行為。

在跨多個繼承樹進行轉換時,請勿使用C樣式的轉換。 如果您使用dynamic_cast則會得到預期的結果:

B* b = new C();
dynamic_cast<A*>(b)->a();

您從B *開始並將其轉換為A *。 由於兩者是不相關的,因此您將深入研究未定義的行為。

((A*) b)是顯式的c樣式強制轉換,無論所指向的類型是什么,都可以使用。 但是,如果嘗試取消引用此指針,則它將是運行時錯誤或不可預測的行為。 這是后者的一個實例。 您觀察到的輸出絕不是安全或保證的。

AB通過繼承互不相關,這意味着指向B的指針不能通過上轉換或下轉換轉換為指向A的指針。

由於ABC兩個不同基數,因此您在此處嘗試執行的操作稱為交叉轉換 可以執行跨dynamic_cast的唯一C ++語言轉換是dynamic_cast 在這種情況下,這是您必須使用的(如果您確實需要)(對嗎?)

B* b = new C(); 
A* a = dynamic_cast<A*>(b);
assert(a != NULL);
a->a();    

下一行是reinterpret_cast,它指向相同的內存,但“假裝”它是另一種對象:

((A*) b)->a();

您真正想要的是一個dynamic_cast,它檢查b到底是哪種對象,並調整內存中的位置以指向:

dynamic_cast<A*>(b)->a()

正如jeffamaphone所提到的,兩個類的相似布局是導致調用錯誤函數的原因。

在C ++中,幾乎從來沒有理由或要求使用C樣式的強制轉換(或其C ++等效的reinterpret_cast <>)。 每當您發現自己想使用兩者之一時,請懷疑您的代碼和/或設計。

我認為您在將B*A*存在一個細微的錯誤,並且行為未定義。 避免使用C樣式強制轉換,而更喜歡C ++強制轉換-在這種情況下為dynamic_cast 由於您的編譯器為數據類型和vtable條目分配存儲空間的方式,您最終找到了另一個函數的地址。

暫無
暫無

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

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