深入了解C++中基于模板的類型擦除
在C\C++中主要有三種類型擦除的方式:
基于void*的類型擦除,如C標準庫的qsort函數(shù)。這中用法在C中是常見的。但因為是通過void*來操作數(shù)據(jù),所以存在類型不安全的問題。
- 函數(shù)原型:void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *))
- 用途:對數(shù)組進行排序
- 類型擦除:base 是一個指向數(shù)組元素的指針,其類型為 void*。這使得 qsort 可以處理任何類型的數(shù)組。
面向?qū)ο蟮念愋筒脸?/strong>,也就是C++中的繼承,通過父類的引用或指針來調(diào)用子類的接口。這樣解決了void*的類型不安全問題,但是繼承也帶來了代碼復雜度提升,以及侵入式設計的問題(子類的實現(xiàn)比如知道父類和其繼承體系)。
基于模板的類型擦除,技術上來說,是編寫一個類,它提供模板的構(gòu)造函數(shù)和非虛函數(shù)接口提供功能;隱藏了對象的具體類型,但保留其行為。典型的就是std::function。
下面是一個示例代碼,實現(xiàn)了通用的任務,這些任務可以是任意的函數(shù)對象。
#include <iostream> #include <memory> // 抽象基類TaskBase作為公共接口不變;其子類TaskModel寫成類模板的形式,其把一個任意類型F的函數(shù)對象function_作為數(shù)據(jù)成員。 struct TaskBase { virtual ~TaskBase() {} virtual void operator()() const = 0; }; template <typename F> struct TaskModel : public TaskBase { F functor_; template <typename U> // 構(gòu)造函數(shù)是函數(shù)模板 TaskModel(U &&f) : functor_(std::forward<U>(f)) { } void operator()() const override { functor_(); } }; // 對TaskModel的封裝 class MyTask { std::unique_ptr<TaskBase> ptr_; public: template <typename F> MyTask(F &&f) { using ModelType = TaskModel<F>; ptr_ = std::make_unique<ModelType>(std::forward<F>(f)); } void operator()() const { ptr_->operator()(); } }; /////////////測試代碼///////////////// // 普通函數(shù) void func1() { std::cout << "type erasure 1" << std::endl; } // 重載括號運算符的類 struct func2 { void operator()() const { std::cout << "type erasure 2" << std::endl; } }; int main() { // 普通函數(shù) MyTask t1{&func1}; t1(); // 輸出"type erasure 1" // 重載括號運算符的類 MyTask t2{func2{}}; t2(); // 輸出"type erasure 2" // Lambda MyTask t3{ []() { std::cout << "type erasure 3" << std::endl; }}; t3(); // 輸出"type erasure 3" return 0; }
總結(jié)下,要實現(xiàn)基于模板的類型擦除主要有三層的代碼。
- 第一層是concept,TaskBase??紤]需要的功能后,以虛函數(shù)的形式提供對應的接口I。
- 第二層是model,TaskModel。這是一個類模板,用來存放用戶提供的類T,T應當語法上滿足接口I;重寫concept的虛函數(shù),在虛函數(shù)中調(diào)用T對應的函數(shù)。
- 第三層是wrapper,對應MyTask。存放一個concept指針p指向model對象m;擁有一個模板構(gòu)造函數(shù),以適應任意的用戶提供類型;以非虛函數(shù)的形式提供接口I,通過p調(diào)用m。
從技術上來說,這種類型擦除的技巧可算是運行時多態(tài)的一種另類實現(xiàn)。它避免了一個類通過繼承這種帶來類間強耦合關系的方式,去滿足某個運行時多態(tài)使用(polymorphic use)的需求。
測試代碼表明,用戶可以把任意的滿足void()簽名接口的函數(shù)對象作為任務,但不需要手動繼承任何的代碼或編寫虛函數(shù)。實現(xiàn)任務類的運行時多態(tài)的代碼被限制在庫的范圍內(nèi),不會以繼承的方式侵入用戶的代碼或其他的庫。
這里還有另一個示例。
首先,定義圖形的概念接口ShapeConcept,接口類中定義了接口函數(shù)Draw。然后,通過模板ShapeModel具體實現(xiàn)了ShapeConcept的概念。最后,定義Shape類來封裝ShapeModel。
這樣,定義了Draw接口的正方形Square或者是通過特化實現(xiàn)了Draw的圓形Circle,都可以統(tǒng)一到Shape下,而不需要繼承它。
#include <iostream> #include <memory> #include <utility> #include <vector> // 圖形的概念接口 struct ShapeConcept { virtual void Draw() const = 0; }; // 圖形概念的具體實現(xiàn) template <typename T> struct ShapeModel : ShapeConcept { ShapeModel(T &&val) : shape_{std::forward<T>(val)} {} void Draw() const override { shape_.Draw(); // 這里假設具體圖形有Draw()成員函數(shù)。如果沒有,需要特化該模板。 } private: // 這里直接存儲具體圖形的值 T shape_; }; // 父類 class Shape { public: template <typename T> Shape(T &&val) : pimpl_{new ShapeModel<T>(std::forward<T>(val))} {} inline void Draw() const { pimpl_->Draw(); } private: std::unique_ptr<ShapeConcept> pimpl_; }; //---------------------正方形------------------------- class Square { public: explicit Square(float side) : side_(side) {} // 正方形的繪圖函數(shù)是一個成員函數(shù) void Draw() const { std::cout << "Draw square of side: " << side_ << std::endl; } private: float side_; }; //---------------------圓----------------------------- class Circle { public: explicit Circle(float radius) : radius_(radius) {} float GetRadius() const { return radius_; } private: float radius_; }; // 圓的繪圖是一個全局函數(shù) void DrawCircle(const Circle &circle) { std::cout << "Draw circle of radius: " << circle.GetRadius() << std::endl; } // 因為圓的繪圖函數(shù)是一個全局函數(shù),所以需要特化 template <> struct ShapeModel<Circle> : ShapeConcept { ShapeModel(Circle &&val) : shape_(std::move(val)) {} void Draw() const override { DrawCircle(shape_); } private: Circle shape_; }; int main() { std::vector<Shape> shapes; shapes.push_back(Circle(1.0)); shapes.push_back(Square(2.0)); for (const auto &shape : shapes) { shape.Draw(); } return 0; }
到此這篇關于深入了解C++中基于模板的類型擦除的文章就介紹到這了,更多相關C++類型擦除內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
visual studio 2019工具里添加開發(fā)中命令提示符的方法
這篇文章主要介紹了visual studio 2019工具里添加開發(fā)中命令提示符的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-03-03養(yǎng)成良好的C++編程習慣之內(nèi)存管理的應用詳解
"養(yǎng)成良好的編程習慣"其實是相當綜合的一個命題,可以從多個角度、維度和層次進行論述和評判,如代碼的風格、效率和可讀性;模塊設計的靈活性、可擴展性和耦合度等等,要試圖把所有方面都闡述清楚必須花很多的精力,而且也不一定能闡述得全面2013-05-05C++中類的三種訪問權(quán)限解析:private、public與protect
這篇文章主要介紹了C++中類的三種訪問權(quán)限解析:private、public與protect,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11C++實現(xiàn)數(shù)據(jù)結(jié)構(gòu)的順序表詳解
這篇文章主要為大家詳細介紹了C++實現(xiàn)動態(tài)順序表,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11