[英]calling inline member in derived class from base class of derived instance
我有兩個類,我們稱它們為A和B,看起來像這樣:
class A {
public:
inline void f1(int n) { f2(n&1); }
inline void f2(int n) { } // no-op in base class
};
class B : public A {
public:
inline void f2(int n) { printf("n=%d\n",n); } // printf for debugging only
};
現在我實例化B,然后調用f1。
int main() {
B myB;
myB.f1(500);
}
我期望發生的事情是,它將在B中調用f1(),它繼承自A,這將轉而使用LSB 500,並將其傳遞給B中的f2(),然后將其打印出來。 相反,發生的是它在A中調用f1()。
為什么這不起作用? 編譯器在編譯時就知道myB是B類,而不是A類。出於以下兩個原因,我無法使f1或f2是虛擬的:首先,由於性能原因,我不能忍受任何函數調用開銷;其次,使它與某些常量參數內聯可以使編譯器經常優化很多代碼。 我曾嘗試使f1()在A中成為內聯虛擬函數,但即使使用-O3
我仍然看到(在一個特定的測試中,與本示例無關的更多代碼)約500字節的匯編代碼,而我只是8字節執行以下等效操作:
class A {
public:
inline void f1(int n) { f2(n&1); }
inline void f2(int n) { } // no-op in base class
};
class B : public A {
public:
inline void f1(int n) { f2(n&1); }
inline void f2(int n) { printf("n=%d\n",n); } // printf for debugging only
};
現在很明顯,我可以將A中的f1復制/粘貼到所有派生類,但是在實際代碼中,最終將是大約10個類,每個函數大約有35個而不是1個,並且保持#include
相同似乎是很愚蠢的做法一遍又一遍地編寫代碼,以避免在很多地方復制此代碼所帶來的難以置信的維護問題。
這里有兩個問題:
在A的上下文中調用f2()
實際上會調用A::f2()
-句點。 如果您想要多態行為,則必須將f2
虛擬化。
您已經假定將f2
虛擬化會增加運行時的開銷。 實際上,如果編譯器可以證明您正在B上調用f1()
,則不會有任何開銷。
證明在這里: https : //godbolt.org/g/2OWPo2
另外,不需要內置關鍵字。 如果在聲明時定義方法的主體,則該方法是隱式內聯的。
此外(與直覺相反), inline
不會導致生成內聯代碼。 它只是警告編譯器它可能會多次看到該定義。
內聯是編譯器將自己進行的優化。
最后,再次查看生成的程序集。 您將看到編譯器甚至沒有費心發出vtable或任何vtable構造。 這幾天的優化程序真的很好 。
實際上,您買不起虛擬功能是很常見的。 在這種情況下,您需要重新設計:
template<typename Impl>
class ABase {
public:
inline void f1(int n) { f2(n&1); }
inline void f2(int n) { static_cast<Impl*>(this)->f2(n); }
};
class AReal : public ABase<AReal> {
inline void f2(int n) { } // no-op here
};
class B : public ABase<B> {
public:
inline void f2(int n) { printf("n=%d\n",n); } // printf for debugging only
};
注意,您仍然不能指望動態多態性,並且AReal和B現在不再相關。 (如果不需要后者,可以通過在ABase中添加非模板庫來解決;如果沒有與虛擬庫相當的開銷,則不能解決前者。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.