深入了解C++中基于模板的類(lèi)型擦除
在C\C++中主要有三種類(lèi)型擦除的方式:
基于void*的類(lèi)型擦除,如C標(biāo)準(zhǔn)庫(kù)的qsort函數(shù)。這中用法在C中是常見(jiàn)的。但因?yàn)槭峭ㄟ^(guò)void*來(lái)操作數(shù)據(jù),所以存在類(lèi)型不安全的問(wèn)題。
- 函數(shù)原型:void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *))
- 用途:對(duì)數(shù)組進(jìn)行排序
- 類(lèi)型擦除:base 是一個(gè)指向數(shù)組元素的指針,其類(lèi)型為 void*。這使得 qsort 可以處理任何類(lèi)型的數(shù)組。
面向?qū)ο蟮念?lèi)型擦除,也就是C++中的繼承,通過(guò)父類(lèi)的引用或指針來(lái)調(diào)用子類(lèi)的接口。這樣解決了void*的類(lèi)型不安全問(wèn)題,但是繼承也帶來(lái)了代碼復(fù)雜度提升,以及侵入式設(shè)計(jì)的問(wèn)題(子類(lèi)的實(shí)現(xiàn)比如知道父類(lèi)和其繼承體系)。
基于模板的類(lèi)型擦除,技術(shù)上來(lái)說(shuō),是編寫(xiě)一個(gè)類(lèi),它提供模板的構(gòu)造函數(shù)和非虛函數(shù)接口提供功能;隱藏了對(duì)象的具體類(lèi)型,但保留其行為。典型的就是std::function。
下面是一個(gè)示例代碼,實(shí)現(xiàn)了通用的任務(wù),這些任務(wù)可以是任意的函數(shù)對(duì)象。
#include <iostream>
#include <memory>
// 抽象基類(lèi)TaskBase作為公共接口不變;其子類(lèi)TaskModel寫(xiě)成類(lèi)模板的形式,其把一個(gè)任意類(lèi)型F的函數(shù)對(duì)象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_();
}
};
// 對(duì)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()();
}
};
/////////////測(cè)試代碼/////////////////
// 普通函數(shù)
void func1()
{
std::cout << "type erasure 1" << std::endl;
}
// 重載括號(hào)運(yùn)算符的類(lèi)
struct func2
{
void operator()() const
{
std::cout << "type erasure 2" << std::endl;
}
};
int main()
{
// 普通函數(shù)
MyTask t1{&func1};
t1(); // 輸出"type erasure 1"
// 重載括號(hào)運(yùn)算符的類(lèi)
MyTask t2{func2{}};
t2(); // 輸出"type erasure 2"
// Lambda
MyTask t3{
[]()
{ std::cout << "type erasure 3" << std::endl; }};
t3(); // 輸出"type erasure 3"
return 0;
}
總結(jié)下,要實(shí)現(xiàn)基于模板的類(lèi)型擦除主要有三層的代碼。
- 第一層是concept,TaskBase??紤]需要的功能后,以虛函數(shù)的形式提供對(duì)應(yīng)的接口I。
- 第二層是model,TaskModel。這是一個(gè)類(lèi)模板,用來(lái)存放用戶(hù)提供的類(lèi)T,T應(yīng)當(dāng)語(yǔ)法上滿(mǎn)足接口I;重寫(xiě)concept的虛函數(shù),在虛函數(shù)中調(diào)用T對(duì)應(yīng)的函數(shù)。
- 第三層是wrapper,對(duì)應(yīng)MyTask。存放一個(gè)concept指針p指向model對(duì)象m;擁有一個(gè)模板構(gòu)造函數(shù),以適應(yīng)任意的用戶(hù)提供類(lèi)型;以非虛函數(shù)的形式提供接口I,通過(guò)p調(diào)用m。
從技術(shù)上來(lái)說(shuō),這種類(lèi)型擦除的技巧可算是運(yùn)行時(shí)多態(tài)的一種另類(lèi)實(shí)現(xiàn)。它避免了一個(gè)類(lèi)通過(guò)繼承這種帶來(lái)類(lèi)間強(qiáng)耦合關(guān)系的方式,去滿(mǎn)足某個(gè)運(yùn)行時(shí)多態(tài)使用(polymorphic use)的需求。
測(cè)試代碼表明,用戶(hù)可以把任意的滿(mǎn)足void()簽名接口的函數(shù)對(duì)象作為任務(wù),但不需要手動(dòng)繼承任何的代碼或編寫(xiě)虛函數(shù)。實(shí)現(xiàn)任務(wù)類(lèi)的運(yùn)行時(shí)多態(tài)的代碼被限制在庫(kù)的范圍內(nèi),不會(huì)以繼承的方式侵入用戶(hù)的代碼或其他的庫(kù)。
這里還有另一個(gè)示例。
首先,定義圖形的概念接口ShapeConcept,接口類(lèi)中定義了接口函數(shù)Draw。然后,通過(guò)模板ShapeModel具體實(shí)現(xiàn)了ShapeConcept的概念。最后,定義Shape類(lèi)來(lái)封裝ShapeModel。
這樣,定義了Draw接口的正方形Square或者是通過(guò)特化實(shí)現(xiàn)了Draw的圓形Circle,都可以統(tǒng)一到Shape下,而不需要繼承它。
#include <iostream>
#include <memory>
#include <utility>
#include <vector>
// 圖形的概念接口
struct ShapeConcept
{
virtual void Draw() const = 0;
};
// 圖形概念的具體實(shí)現(xiàn)
template <typename T>
struct ShapeModel : ShapeConcept
{
ShapeModel(T &&val) : shape_{std::forward<T>(val)} {}
void Draw() const override
{
shape_.Draw(); // 這里假設(shè)具體圖形有Draw()成員函數(shù)。如果沒(méi)有,需要特化該模板。
}
private:
// 這里直接存儲(chǔ)具體圖形的值
T shape_;
};
// 父類(lèi)
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ù)是一個(gè)成員函數(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_;
};
// 圓的繪圖是一個(gè)全局函數(shù)
void DrawCircle(const Circle &circle)
{
std::cout << "Draw circle of radius: " << circle.GetRadius() << std::endl;
}
// 因?yàn)閳A的繪圖函數(shù)是一個(gè)全局函數(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;
}
到此這篇關(guān)于深入了解C++中基于模板的類(lèi)型擦除的文章就介紹到這了,更多相關(guān)C++類(lèi)型擦除內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)超市計(jì)價(jià)收款系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)超市計(jì)價(jià)收款系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
C++11新特性之智能指針(shared_ptr/unique_ptr/weak_ptr)
這篇文章主要介紹了C++11新特性之智能指針,包括shared_ptr, unique_ptr和weak_ptr的基本使用,感興趣的小伙伴們可以參考一下2016-08-08
深入理解 C++ 的 std::initializer_list及使用場(chǎng)景分析
本文介紹了C++11引入的std::initializer_list模板類(lèi),它作為統(tǒng)一初始化語(yǔ)法的重要橋梁,封裝同類(lèi)型常量值,常用于容器初始化、函數(shù)參數(shù)傳遞和自定義類(lèi)支持花括號(hào)初始化,感興趣的朋友跟隨小編一起看看吧2025-10-10
C語(yǔ)言函數(shù)棧幀的創(chuàng)建和銷(xiāo)毀介紹
大家好,本篇文章主要講的是C語(yǔ)言函數(shù)棧幀的創(chuàng)建和銷(xiāo)毀介紹,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2021-12-12
C語(yǔ)言之實(shí)現(xiàn)單鏈表指定結(jié)點(diǎn)的插入方式
這篇文章主要介紹了C語(yǔ)言之實(shí)現(xiàn)單鏈表指定結(jié)點(diǎn)的插入方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07

