模板与泛型编程常用技巧
目录
一、基础阶段
1.1 函数模板
template<typename T>
T Max(const T& t1, const T& t2)
{
return t1 < t2 ? t2 : t1;
}
template<typename T>
void Swap(T& t1, T& t2)
{
T temp = std::move(t1);
t1 = std::move(t2);
t2 = std::move(temp);
}
1.2 类模板
template<typename T>
struct Stack
{
private:
vector<T> elments;
public:
void Push(const T& obj)
{
elments.push_back(obj);
}
void Pop()
{
if (elments.size() > 0)
{
elments.pop_back();
}
else
{
throw out_of_range("empty stack");
}
}
T GetTop()
{
if (elments.size() > 0)
{
return elments.back();
}
else
{
throw out_of_range("empty stack");
}
}
};
template<typename T,size_t nSize>
class Array
{
private:
T data[nSize];
public:
T& operator[](int nIndex)
{
return data[nIndex];
}
};
int main()
{
Array<int, 5> a;
for (int i = 0;i<5;i++)
{
a[i] = i + 1;
cout << a[i] << " ";
}
system("pause");
return 0;
}
结果:
1.3 模板特化与偏特化
模板特化(Template Specialization)和偏特化(Partial Specialization)是C++模板编程中的高级特性,允许你为特定类型或类型组合提供特殊的模板实现
模板特化
模板特化是指为模板的特定参数提供完全不同的实现。分为函数模板特化和类模板特化。
函数模板特化
// 通用模板
template <typename T>
void printType() {
std::cout << "Generic type\n";
}
// 特化版本 - 针对int类型
template <>
void printType<int>() {
std::cout << "int type\n";
}
// 使用示例
printType<double>(); // 输出: Generic type
printType<int>(); // 输出: int type
类模板特化
// 通用模板
template <typename T>
class Container {
public:
void describe() {
std::cout << "Generic container\n";
}
};
// 特化版本 - 针对char类型
template <>
class Container<char> {
public:
void describe() {
std::cout << "Character container\n";
}
};
// 使用示例
Container<double> c1;
c1.describe(); // 输出: Generic container
Container<char> c2;
c2.describe(); // 输出: Character container
模板偏特化
偏特化(也称为部分特化)允许你为模板的部分参数提供特殊实现。注意:偏特化只适用于类模板,不适用于函数模板。
基本偏特化
// 通用模板
template <typename T, typename U>
class Pair {
public:
void describe() {
std::cout << "Generic pair\n";
}
};
// 偏特化 - 当两个类型相同时
template <typename T>
class Pair<T, T> {
public:
void describe() {
std::cout << "Pair of same types\n";
}
};
// 使用示例
Pair<int, double> p1;
p1.describe(); // 输出: Generic pair
Pair<float, float> p2;
p2.describe(); // 输出: Pair of same types
// 通用模板
template <typename T>
class Box {
public:
void describe() {
std::cout << "Box of value type\n";
}
};
// 偏特化 - 针对指针类型
template <typename T>
class Box<T*> {
public:
void describe() {
std::cout << "Box of pointer type\n";
}
};
// 使用示例
Box<int> b1;
b1.describe(); // 输出: Box of value type
Box<int*> b2;
b2.describe(); // 输出: Box of pointer type
结果:
多参数偏特化
// 通用模板
template <typename T, typename U, typename V>
class Triple {
public:
void describe() {
std::cout << "Generic triple\n";
}
};
// 偏特化 - 当第一个和第三个类型相同时
template <typename T, typename U>
class Triple<T, U, T> {
public:
void describe() {
std::cout << "Triple with first and last same type\n";
}
};
// 使用示例
Triple<int, double, char> t1;
t1.describe(); // 输出: Generic triple
Triple<float, int, float> t2;
t2.describe(); // 输出: Triple with first and last same type
示例:封装一个判断是否为指针的工具
template<typename T>
struct Is_pointer {
static constexpr bool value = false;
};
template<typename T>
struct Is_pointer<T *> {
static constexpr bool value = true;
};
int main()
{
cout << Is_pointer<int>::value << endl;
cout << Is_pointer<int*>::value << endl;
system("pause");
return 0;
}
结果:
1.4模板编译模型
模板编译模型是指C++编译器处理模板代码的方式和规则,它决定了模板如何被实例化、链接以及最终生成可执行代码。
包含模型- 最常用
核心思想:模板定义必须在使用它的每个翻译单元中都可见
特点:
- 模板的声明和定义都放在头文件中
- 每个使用模板的源文件都会包含完整定义
- 编译器在每个翻译单元实例化所需特化
示例:
// vector.h
template <typename T>
class Vector {
public:
void push_back(const T& value) {
// 实现直接写在头文件中
}
};
显式实例化模型
核心思想:提前显式声明需要的模板实例化
** mytemplate.h**
#pragma once
// mytemplate.h
template <typename T>
T add(T a, T b); // 只有声明
mytemplate.cpp
#include "Mytemplate.h"
// mytemplate.cpp
template <typename T>
T add(T a, T b) { return a + b; }
// 显式实例化
template int add<int>(int, int);
template double add<double>(double, double);
main.cpp
#include <iostream>
#include "Mytemplate.h"
using namespace std;
int main()
{
cout<<add(100, 200) << endl;
cout << add(12.3, 22.65) << endl;
system("pause");
return 0;
}
特点:
- 模板定义可放在.cpp文件
- 需预先知道所有需要的实例化
- 减少重编译但灵活性低
1.5 变量模板
变量模板的基本定义形式:
template<typename T>
constexpr T pi = T(3.1415926535897932385L); // 变量模板声明和定义
使用方式:
float f = pi<float>; // 3.14159265f
double d = pi<double>; // 3.141592653589793
long double ld = pi<long double>; // 3.1415926535897932385L
基本用途
(1) 类型相关的常量
#include <numeric>
template<typename T>
constexpr T max_value = std::numeric_limits<int>::max();
int main()
{
cout << max_value<int> << endl;
cout << max_value<double> << endl;
system("pause");
return 0;
}
结果:
(2) 简化类型特征访问
template<typename T>
constexpr bool Is_integral_v = std::is_integral<T>::value;
int main()
{
static_assert(Is_integral_v<int>); // 通过
static_assert(!Is_integral_v<float>); // 通过
system("pause");
return 0;
}
1.6 别名模板
别名模板是C++11引入的一项重要特性,它允许我们为模板创建类型别名,极大地简化了复杂类型表达式的书写,提高了代码的可读性和可维护性。
别名模板(Alias Template)是一种可以参数化的typedef,它能够为模板类型创建别名:
基本语法
template<模板参数列表>
using 别名 = 类型表达式;
template<typename T>
using Vec = std::vector<T>; // Vec<T> 是 std::vector<T> 的别名
基本使用
Vec<int> v1; // 等价于 std::vector<int>
Vec<std::string> v2; // 等价于 std::vector<std::string>
1.7 模板模板参数
模板模板参数是指接受一个类模板作为参数的模板参数。它允许你在不指定具体类型的情况下,传递整个模板作为参数。
基本语法
template <template <typename> class Container>
class Wrapper {
Container<int> data; // 使用传入的模板实例化
};
使用示例
template <typename T>
class MyVector { /*...*/ };
Wrapper<MyVector> w; // 将MyVector模板作为参数传递
现在有一个需求,创建一个TyClass的类模板,这个类模板,有一个成员变量myc,这个成员变量是一个容器(可能是一个vector或者list等)。现在希望在实例化这个类模板时候能够通过模板参数指定myc是什么类型的容器,以及指定这个容器中所装的元素类型。比如,
TyClass<int,vector> myvectobj;
TyClass<double,list> mylistobj;
实现代码:
template<typename T,
template<typename>
class Container = std::vector>
class TyClass
{
private:
Container<T> myc;
public:
void Push(const T& obj)
{
myc.push_back(obj);
}
void Print()
{
for (const auto &node :myc)
{
cout << node << endl;
}
}
};
int main()
{
TyClass<int> tc;
tc.Push(1);
tc.Push(2);
tc.Push(3);
tc.Push(4);
tc.Print();
system("pause");
return 0;
}
结果:
二、中级阶段
2.1 SFINAE
SFINAE(Substitution Failure Is Not An Error)是C++模板元编程中的核心机制,它允许编译器在模板参数替换失败时优雅地忽略该候选而非报错。
template<class T>
void f(typename T::type i) {}; // 当T没有::type成员时触发SFINAE
struct X { using type = int; };
struct Y {};
int main()
{
f<X>(0); // 匹配
f<Y>(0); // SFINAE忽略,若无其他重载则报未找到匹配函数
system("pause");
return 0;
}
结果:
编译器并不认为这个函数模板有错,这就是所谓的“替换失败并不是一个错误”,对于Y类型,并没有type,但是对于其他的类型可能就存在,比如X。但是为什么会报“未找到匹配的重载函数”呢?这是由于函数模板不匹配,编译器又找不到其他适合的f(),所以编译器才报错.
2.2 enable_if:SFINAE应用
标准定义
template<bool B, class T = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> {
using type = T; // 只有当B=true时才有type成员
};
辅助类型别名 (C++14起)
template<bool B, class T = void>
using enable_if_t = typename enable_if<B, T>::type;
示例:
template<bool b,typename T = void>
struct enableIF
{
};
template<typename T>
struct enableIF<true, T>
{
using Type = T;
};
template<typename T>
typename enableIF<std::is_integral<T>::value, T>::Type Incred(T a)
{
a++;
return a;
}
int main()
{
cout << Incred(12) << endl;;
//cout << Incred(12.2) << endl; SFINAE
system("pause");
return 0;
}
结果:
2.3 void_t技术:SFINAE应用
std::void_t是C++17中引入的,它其实是一个别名模板,源码非常简单,大概如下:
template<tpename...Args>
using void_t = void;
示例:
struct hasType {
using intType = int;
void fun() {}
};
struct noType {
void fun() {}
};
//泛化版本
template<typename T,typename U = void_t<>>
struct HasTypeMem :std::false_type
{
};
//特化版本
template<typename T>
struct HasTypeMem<T, std::void_t<typename T::intType> > :std::true_type
{
};
int main()
{
cout << HasTypeMem<noType>::value << endl;
cout << HasTypeMem<hasType>::value << endl;
system("pause");
return 0;
}
结果:
SFINAE机制:
- 如果 T 没有 intType,typename T::intType会导致替换失败,但这不是错误,编译器会回退到泛化版本(false_type)。
- 如果 T 有 intType,替换成功,选择特化版本(true_type)。
2.4 萃取
类型萃取是一种编译时类型检查与操作技术,通过模板类和模板特化实现,主要用于:
- 检查类型特性(如是否为指针、是否为算术类型等)
- 修改类型特性(如移除const、添加引用等)
- 根据类型特性实现条件编译
类型分类检查
std::is_void<T> // 是否为void类型
std::is_integral<T> // 是否为整型
std::is_floating_point<T> // 是否为浮点型
std::is_array<T> // 是否为数组
类型修饰检查
std::is_const<T> // 是否有const限定
std::is_pointer<T> // 是否为指针
std::is_reference<T> // 是否为引用
类型关系
std::is_same<T, U> // 类型是否相同
std::is_base_of<Base, Derived> // 是否为基类
std::is_convertible<From, To> // 是否可隐式转换
示例:
template<typename T>
struct TraitsType;
template<>
struct TraitsType<char>
{
using RetType = int;
};
template<>
struct TraitsType<int>
{
using RetType = __int64;
};
template<typename T>
auto GetSum(T* begin, T* end)
{
using retType = typename TraitsType<T>::RetType;
cout << typeid(T).name() << "==>" << typeid(retType).name() << endl;
retType sum{};
for (;;)
{
sum += (*begin);
if (begin == end)
{
break;
}
begin++;
}
return sum;
}
int main()
{
int a1[] = { 1,3,5,7 };
int a2[] = { 500000000,500000000,700000000 };
char a3[] = "abc";
cout << GetSum(&a1[0], &a1[3]) << endl;
cout << GetSum(&a2[0], &a2[2]) << endl;
int nsum = (int)GetSum(&a3[0], &a3[2]);
cout << nsum << endl;
system("pause");
return 0;
}
结果:
自己实现is_void
template<typename T>
struct Is_void
{
static const int value = 0;
};
template<>
struct Is_void<void>
{
static const int value = 1;
};
int main()
{
cout << Is_void<int>::value << endl;
cout << Is_void<void>::value << endl;
system("pause");
return 0;
}
结果:
自己实现is_same
template<typename T1,typename T2>
struct Is_same
{
static const int value = 0;
};
template<typename T1>
struct Is_same<T1,T1>
{
static const int value = 1;
};
int main()
{
cout << Is_same<int,int>::value << endl;
cout << Is_same<void,int>::value << endl;
system("pause");
return 0;
}
结果:
2.5 可变参数模板
可变参数模板是C++11引入的一项强大特性,允许模板接受任意数量的模板参数。它是实现泛型编程的重要工具。
可变参数模板使用省略号(…)语法来表示可以接受任意数量的参数:
template<typename... Args>
class MyClass {};
template<typename... Args>
void myFunction(Args... args) {}
其中:
Args 是模板参数包
args 是函数参数包
使用示例
1.递归展开
最常见的展开方式是递归:
void Print()
{
cout << endl;
}
template<typename T,typename...U>
void Print(T ft, U...args)
{
cout << ft << " ";
Print(args...);
}
int main()
{
Print("I","love","China",1314);
system("pause");
return 0;
}
结果:
2. 使用折叠表达式 (C++17)
C++17引入了折叠表达式,简化了可变参数模板的使用:
template<typename...U>
auto Sum(U...args)
{
return (... + args);
}
int main()
{
auto nSum = Sum(1, 2, 3, 4, 66.5);
cout << nSum << endl;
system("pause");
return 0;
}
结果:
3. sizeof… 运算符
可以获取参数包中的参数数量:
template<typename T,typename...U>
auto Sum2(T ft, U...args)
{
if constexpr (sizeof...(args) > 0)
{
return ft + Sum2(args...);
}
else
{
return ft;
}
}
4.应用场景
元组(Tuple)实现:
template<typename... Types>
class Tuple;
完美转发:
template<typename... Args>
void forwarder(Args&&... args) {
someFunction(std::forward<Args>(args)...);
}
工厂函数:
template<typename T, typename... Args>
T* create(Args... args) {
return new T(args...);
}
打印任意数量参数:
template<typename... Args>
void log(Args... args) { /*...*/ }
2.6 完美转发
完美转发是C++11引入的一项重要特性,它允许函数模板将其参数无损地转发给其他函数,保持原始参数的值类别(左值/右值)。
完美转发依赖于两个关键特性:
- 右值引用(T&&)
- 引用折叠规则(Reference Collapsing)
格式如下:
template<typename T>
void wrapper(T&& arg) {
// 保持arg的值类别转发给target
target(std::forward<T>(arg));
}
引用折叠:
类型定义 折叠结果
T& & T&
T& && T&
T&& & T&
T&& && T&&
示例:
template<typename T>
void func(T&& param) {
cout << "传入的是右值" << endl;
}
template<typename T>
void funcMiddle(T&& param) {
func(std::forward<T>(param));
}
int main()
{
int num = 2021;
funcMiddle(num);
funcMiddle(2022);
system("pause");
return 0;
}
结果:
三、高级阶段
3.1 C++模板设计模式
策略模式
template<typename T, typename AllocationPolicy>
class ManagedContainer {
AllocationPolicy allocator;
public:
void* allocate(size_t size) {
return allocator.allocate(size);
}
// ...其他成员函数
};
// 内存分配策略
struct MallocPolicy {
void* allocate(size_t size) { return malloc(size); }
};
struct NewPolicy {
void* allocate(size_t size) { return new char[size]; }
};
int main()
{// 使用
ManagedContainer<int, MallocPolicy> mc;
int *pArray = (int *)mc.allocate(100);
pArray[0] = 1;
pArray[1] = 2;
pArray[2] = 3;
cout << pArray[0] << " " << pArray[1] << " " << pArray[2] << endl;
system("pause");
return 0;
}
奇异递归模板模式
静态多态实现
template<typename Derived>
class Base
{
public:
void Interface()
{
Derived* pDerived = static_cast<Derived*>(this);
pDerived->impl();
}
};
class Myderived :public Base<Myderived>
{
public:
void impl()
{
cout << "this is Myderived::impl()\n";
}
};
template<typename T>
void Process(Base<T>* pBase)
{
pBase->Interface();
}
int main()
{// 使用
Myderived d;
Process(&d);
system("pause");
return 0;
}
应用示例:
template<typename T>
class Base
{
public:
void process()
{
GetSub()->process_impl();
}
private:
T* GetSub()
{
T* pSub = static_cast<T*>(this);
return pSub;
}
};
class Sub1 :public Base<Sub1>
{
public:
void process_impl()
{
cout << "Sub1::process_impl()\n";
}
~Sub1()
{
cout << "~Sub1()\n";
}
};
class Sub2 :public Base<Sub2>
{
public:
void process_impl()
{
cout << "Sub2::process_impl()\n";
}
~Sub2()
{
cout << "~Sub2()\n";
}
};
template<typename T>
void Process(Base<T>* p)
{
p->process();
}
int main()
{
Sub1* ps1 = new Sub1;
Sub2* ps2 = new Sub2;
Process(ps1);
Process(ps2);
delete ps1;
delete ps2;
system("pause");
return 0;
}
结果:
四、模板实践
4.1智能指针实现
template<typename T>
class SharedPtr
{
private:
T* m_ptr;
int* m_pRefCount;
mutex* m_mtx;
void Release()
{
(*m_pRefCount)--;
if (*m_pRefCount == 0)
{
delete m_ptr;
m_ptr = nullptr;
delete m_pRefCount;
m_pRefCount = nullptr;
}
}
public:
SharedPtr(T* ptr)
{
m_ptr = ptr;
m_pRefCount = new int(1);
m_mtx = new mutex;
}
~SharedPtr()
{
Release();
}
SharedPtr(const SharedPtr<T>& obj)
{
m_ptr = obj.m_ptr;
m_pRefCount = obj.m_pRefCount;
m_mtx = obj.m_mtx;
m_mtx->lock();
(*m_pRefCount)++;
m_mtx->unlock();
}
SharedPtr<T>& operator=(const SharedPtr<T>& obj)
{
if (this != &obj)
{
Release();
m_ptr = obj.m_ptr;
m_pRefCount = obj.m_pRefCount;
m_mtx = obj.m_mtx;
m_mtx->lock();
(*m_pRefCount)++;
m_mtx->unlock();
}
return *this;
}
T* operator->()
{
return m_ptr;
}
T& operator*()
{
return *m_ptr;
}
int UseCount()
{
return m_ptr ? *m_pRefCount : 0;
}
};
class Ty
{
public:
Ty(int a,int b):m_a(a),m_b(b) { cout << "Ty()\n"; }
~Ty() { cout << "~Ty()\n"; }
void Print()
{
cout << "m_a:" << m_a << " m_b:" << m_b << endl;
}
private:
int m_a;
int m_b;
};
int main()
{// 使用
{
SharedPtr<Ty> sp(new Ty(10, 20));
sp->Print();
cout << "cout:" << sp.UseCount() << endl;
SharedPtr<Ty>sp2(sp);
cout << "cout:" << sp.UseCount() << endl;
SharedPtr<Ty> sp3 = sp2;
cout << "cout:" << sp.UseCount() << endl;
sp3->Print();
SharedPtr<Ty> sp4(new Ty(1,2));
sp4 = sp3;
cout << "cout:" << sp.UseCount() << endl;
}
system("pause");
return 0;
}
结果: