[英]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樣式強制轉換,無論所指向的類型是什么,都可以使用。 但是,如果嘗試取消引用此指針,則它將是運行時錯誤或不可預測的行為。 這是后者的一個實例。 您觀察到的輸出絕不是安全或保證的。
A
和B
通過繼承互不相關,這意味着指向B
的指針不能通過上轉換或下轉換轉換為指向A
的指針。
由於A
和B
是C
兩個不同基數,因此您在此處嘗試執行的操作稱為交叉轉換 。 可以執行跨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.