對於raii的虛函式的效率問題,是因為沒有辦法確定指標的具體型別,才採用了執行時動態判斷,但是這樣會導致效率降低。看過一篇知乎的文章說使用benchmark測得普通函式與虛函式的差距在8-10倍,具體原因跟很多方面有關。
crtp就是為了優化掉這個問題而提出的一種模式,將原來的虛繼承改為了繼承「定製的「模版類。這樣的話原本虛函式的呼叫,改為了使用強轉指標型別並呼叫其成員函式的方式實現了,對於型別的判斷從原本的「執行時「變成了「編譯時「。
下面附上四個例子來一步步看看怎麼實現:
最簡單的實現了crtp
中心思想是:使用模版生成基類來讓子類繼承
為了實現模版的基類對子類函式的呼叫,需要模版基類中得到子類的資訊,所以繼承時候要傳入子類
具體不同:
#ifndef __h_crtp1__
#define __h_crtp1__
#include
namespace crtp1;~
base()
;void
func()
;};class derived : public base;~
derived()
;void
func()
;};template
void
global_virtual_func
(base
* p)
;void
test()
;};#endif
上面還存在一些問題:
純虛函式沒有實現
如果子類沒有實現func,而基類強轉之後直接進行呼叫,呼叫回基類函式無限遞迴
析構函式不能正確使用
base 與 derived 之間沒有虛繼承關係,所以用基類指標釋放時候呼叫不到子類
所以有了一些解決辦法:
1.純虛函式使用impl字尾的函式來實現具體內容,並且在基類中有乙個預設版本,就不會出現問題。
或者純虛函式還是使用純虛函式來實現。
2.既然沒有正常的呼叫到子類析構函式,那麼就顯示的宣告destroy並呼叫來實現子類的析構
或者使用虛函式實現析構函式。
3.強轉的地方可以放在乙個函式來統一實現。
#ifndef __h_crtp2__
#define __h_crtp2__
#include
namespace crtp2;~
base()
;void
func()
;void
func_impl()
;// 1.預設的實現
void
destroy()
;// 2.實現子類的析構
private:
//3.私有化的強轉函式
inline d*
_derived()
;};class derived : public base;~
derived()
;void
func_impl()
;};template
void
global_virtual_func
(base
* p)
;void
test()
;};#endif
是保持了純虛函式和虛析構函式的一種
還是存在一些問題的:
crtp是為了避免虛函式的效率問題而實現的
暫時還沒有確定虛函式效率問題是哪?
1.虛表查詢麼?
2.虛表跳轉打亂指令流水線麼?
3.虛表跳轉不好**麼?
4.cache miss導致麼?
既然要抽象層的純虛函式,就已經沒辦法避免效率問題,所以既然用crtp提公升效率就最好避免虛函式的出現
#ifndef __h_crtp3__
#define __h_crtp3__
#include
namespace crtp3
;//虛析構函式
virtual void
func()
=0;//指定了子類要實現的介面};
template
class base : public absbase;~
base()
;// 其他不再抽象層中的虛函式 還是原方法加
private:
inline d*
_derived()
;};class derived : public base;~
derived()
;void
func()
;};template
void
global_virtual_func
(base
* p)
;void
test()
;};#endif
還有一種可能是子類繼承的時候傳的是另乙個子類的名字,也是不正常的問題,
但是這種問題只有在顯式呼叫的時候才能報錯
為了讓它暴露在編譯階段,所以要將基類中本應該作為protected的成員(為了繼承給子類用)變為
private(宣告子類為友元類的方式達到子類訪問的目的)這樣的話
子類d1 繼承自基類base,而其中基類的友元類為d,所以當d1操作基類的私有變數時就會產生錯誤
#ifndef __h_crtp4__
#define __h_crtp4__
#include
namespace crtp4;~
base()
;void
func()
;void
func_impl()
;void
destroy()
;// 原繼承方式
//protected:
// int _value;
// 新方式:私有化加友元
private:
int _value;
// 私有化
friend d;
// 宣告子類為友元類
private:
inline d*
_derived()
;};class derived : public base;~
derived()
;void
func_impl()
;};/* 錯誤的繼承方式 子類與傳入的子類型別不同
class derived1 : public base;
~derived1();
void func_impl();
};*/
template
void
global_virtual_func
(base
* p)
;void
test()
;};#endif
簡單的乙個demo 以及 標註出從虛繼承體系改過來的時候要注意的不同點
#ifndef __h_crtp_demo__
#define __h_crtp_demo__
#include
namespace crtp_demo
; virtual ~
base()
;// 2.析構函式為虛函式
void
func()
;void
func_impl()
;// 3.inte***ce implement 分離, 注意呼叫
private:
inline d*
_derived()
;// 4.強轉為子類
friend d;
// 5.友元許可權
private:
int _value;
// 6.protected 私有化};
class derived : public base;~
derived()
;void
func_impl()
;};template
void
global_virtual_func
(base
* p)
;void
test()
;};#endif
參考鏈結 靜態多型和CRTP
千萬不要忘記多型還有靜態多型 靜態多型有 1.函式過載。2.模板方法 crtp也是一種實現靜態多型的方法 template typename t class base private void methodimpl class extend1 public base class extend2 pu...
靜態多型 動態多型
又稱編譯期多型,即在系統編譯期間就可以確定程式將要執行哪個函式。例如 函式過載,通過類成員運算子指定的運算。函式過載示例 class a a int x void f void f int x class b void f void f int x 以上,類a中兩個a 是函式過載,兩個f 是函式過載...
C 多型 靜態多型與動態多型
多型 顧名思義,多型就是多種形態,也就是對不同物件傳送同乙個訊息,不同物件會做出不同的響應。並且多型分為靜態多型和動態多型。靜態多型就是在系統編譯期間就可以確定程式執行到這裡將要執行哪個函式,例如 函式的過載,物件名加點操作符執行成員函式等,都是靜態多型,其中,過載是在形成符號表的時候,對函式名做了...