C++設(shè)計模式之CRTP的使用
什么是CRTP
CRTP全稱是curious recurring template pattern,即奇異遞歸模版模式,是一種c++的設(shè)計模式,精巧地結(jié)合了繼承和模板編程的技術(shù)??梢杂脕斫oc++的class提供額外功能、實現(xiàn)靜態(tài)多態(tài)等。
在CRTP之前只聽說C++通過指針實現(xiàn)了動態(tài)多態(tài),現(xiàn)在居然搞出了一個靜態(tài)多態(tài)的東西出來?不得不感慨C++真是一門高深莫測的語言...
動態(tài)多態(tài)
在了解靜態(tài)多態(tài)之前我們先來回顧一下動態(tài)多態(tài),C++ 通過類的繼承與虛函數(shù)的動態(tài)綁定,實現(xiàn)了多態(tài)。這種特性,使得我們能夠用基類的指針,訪問子類的實例。
#include <iostream> class Animal{ public: // 注意需要添加virtual關(guān)鍵字 virtual void run(){ std::cout << "Animal run" << std::endl; } }; class Cat:public Animal{ public: void run() override{ std::cout << "Cat run" << std::endl; } }; int main() { std::vector<Animal> animalVec; animalVec.emplace_back(Animal()); // 非指針的形式,其實內(nèi)部調(diào)用的還是Animal的run animalVec.emplace_back(Cat()); for (auto animal:animalVec) { animal.run(); } // 動態(tài)多態(tài)需要通過指針的形式實現(xiàn) std::vector<Animal*> animalVecPtr; animalVecPtr.push_back(new Animal()); animalVecPtr.push_back(new Cat()); for (auto animal:animalVecPtr) { animal->run(); } return 0; }
CRTP的一個重要功能就是用來實現(xiàn)靜態(tài)多態(tài),CRTP在編譯階段就將子類類型以模版的形式傳遞到父類,以便在編譯階段實現(xiàn)多態(tài)性,這就是靜態(tài)多態(tài)。
既然有了動態(tài)多態(tài),為什么還需要靜態(tài)多態(tài)呢?答案是精益求精,為了效率而生...
我們知道動態(tài)多態(tài)是基于虛函數(shù)的形式在運行時進行動態(tài)綁定的,因此每次運行時都需要查詢虛函數(shù)表,所以動態(tài)綁定會降低程序的執(zhí)行效率。 為了兼顧多態(tài)與效率,就提出了CRTP。
CRTP的使用
我們先來看看在cppreference中是如何使用CRTP的
下面我們依然使用上面Animal的例子通過CRTP的方式實現(xiàn)靜態(tài)多態(tài)。
首先我們按照官方的例子,依瓢畫葫蘆:
#include <iostream> template < class T > class Animal{ public: virtual ~Animal(){ }; // CRTP這里已經(jīng)不需要使用virtual關(guān)鍵字了 void run(){ (static_cast<T*>(this))->run(); } }; class Cat:public Animal<Cat>{ public: void run(){ std::cout << "Cat run" << std::endl; } }; class Dog:public Animal<Dog>{ public: void run(){ std::cout << "Dog run" << std::endl; } }; int main() { Animal<Cat>* cat = new Cat; cat->run(); delete cat; Animal<Dog>* dog = new Dog; dog->run(); delete dog; return 0; }
程序運行起來后打印如下:
可以發(fā)現(xiàn)通過CRTP我們不使用關(guān)鍵字virtual
也能實現(xiàn)了通過父類指針調(diào)用子類方法效果,這就是靜態(tài)多態(tài)的優(yōu)點,它比動態(tài)多態(tài)更高效,更安全。
通過上面的例子我們總結(jié)一下使用CRTP的三個重要步驟:
- 繼承自模版類,因為用到了繼承,因此析構(gòu)函數(shù)需要用virtual修飾,以避免內(nèi)存泄露。
- 子類將自身通過模板參數(shù)傳遞給父類。
- 父類通過static_cast關(guān)鍵字將模板參數(shù)靜態(tài)轉(zhuǎn)化成子類,然后調(diào)用子類的鴨子模型方法。
一般來說將父類轉(zhuǎn)換成子類一般使用的是dynamic_cast,而CRTP是在編譯期間就已經(jīng)明確知道了子類的具體類型,因此直接使用static_cast更為高效。 這也正是CRTP這種設(shè)計的一大精髓。
通過仔細對比我們動態(tài)多態(tài)和靜態(tài)多態(tài)的兩個例子我們發(fā)現(xiàn)還是有點不一樣的,我們在動態(tài)多態(tài)中將Animal的指針添加到了std::vector中去,那么我們的CRTP能否也這樣做呢? 我們來試一下:
#include <iostream> template<class T> class Animal { public: virtual ~Animal() { }; // CRTP這里已經(jīng)不需要使用virtual關(guān)鍵字了 void run() { (static_cast<T *>(this))->run(); } }; class Cat : public Animal<Cat> { public: void run() { std::cout << "Cat run" << std::endl; } }; class Dog : public Animal<Dog> { public: void run() { std::cout << "Dog run" << std::endl; } }; int main() { std::vector<Animal<Cat>*> animalVec; animalVec.emplace_back(new Cat()); // 報錯了,因為vector存放的數(shù)據(jù)類型是Animal<Cat> animalVec.emplace_back(new Dog()); for (auto animal: animalVec) { animal->run(); } return 0; }
我們發(fā)現(xiàn)報錯了,因為Animal和Animal不是同樣的數(shù)據(jù)類型,不能同時放入同一個vector中去。 既然問題的根源是他們不是同樣的數(shù)據(jù)類型,那么我們將它們變成同樣的數(shù)據(jù)類型不就是行了嗎?那么怎么把它們變成同樣的數(shù)據(jù)類型呢?
讓它們繼承一個共同的基類即可。這樣就是動態(tài)多態(tài)與靜態(tài)多態(tài)結(jié)合使用的例子了。
實例代碼如下:
#include <iostream> class BaseAnimal { public: virtual ~BaseAnimal() { }; virtual void run() = 0; }; template<class T> class Animal: public BaseAnimal{ public: virtual ~Animal() { }; // CRTP這里已經(jīng)不需要使用virtual關(guān)鍵字了 void run() override{ (static_cast<T *>(this))->run(); } }; class Cat : public Animal<Cat> { public: void run() override { std::cout << "Cat run" << std::endl; } }; class Dog : public Animal<Dog> { public: void run() override { std::cout << "Dog run" << std::endl; } }; int main() { std::vector<BaseAnimal*> animalVec; animalVec.emplace_back(new Cat()); // 報錯了,因為vector存放的數(shù)據(jù)類型是Animal<Cat> animalVec.emplace_back(new Dog()); for (auto animal: animalVec) { animal->run(); } return 0; }
這樣一來,我們通過CRTP與虛函數(shù)結(jié)合,即保留了動態(tài)多態(tài)的各種特性,也減少了部分虛函數(shù)的查找開銷。
到此這篇關(guān)于C++設(shè)計模式之CRTP的使用的文章就介紹到這了,更多相關(guān)C++設(shè)計模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言實現(xiàn)通訊錄系統(tǒng)課程設(shè)計
這篇文章主要為大家詳細介紹了C語言實現(xiàn)通訊錄系統(tǒng)課程設(shè)計,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07關(guān)于C++中的友元函數(shù)的一些總結(jié)
以下是對C++中的友元函數(shù)進行了詳細的總結(jié)介紹,需要的朋友可以過來參考下2013-09-09VSCode 配置C++開發(fā)環(huán)境的方法步驟
這篇文章主要介紹了VSCode 配置C++開發(fā)環(huán)境的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03