当前位置: 首页 > news >正文

C++泛型编程(一):模板详解

1.泛型编程的优点

  • 代码复用 :避免为不同类型写重复代码
  • 类型安全 :编译期类型检查
  • 性能优化 :模板在编译期展开,没有运行时开销
  • 灵活性 :可以适应不同的数据类型

2.模板基础

1.函数模板

// 基本函数模板示例
template <typename T>
T max(T a, T b) {return a > b ? a : b;
}
  • 编译器会为每个使用的类型生成对应的函数
  • 可以显式指定类型: max<int>(10, 20)
  • 支持类型推导: max(10, 20),省略==<int>==

2.类模板

template <typename T>
class MVector {T* data;size_t size;
public:MVector() : data(nullptr), size(0) {}void push_back(const T& value);
};
  • 必须显式指定类型: MVector<int> vec;
  • 成员函数在使用时才实例化
  • 可以有多个类型参数

3.高级特性

1.模板特化

模板特化允许开发者为特定类型类型组合提供专门的实现。当通用模板无法满足特定需求时,特化模板可以调整行为以处理特定的情况

C++ 支持全特化(Full Specialization)偏特化(Partial Specialization),但需要注意的是,函数模板不支持偏特化,只能进行全特化

1.全特化

全特化是针对模板参数的完全特定类型组合。它提供了模板的一个特定版本,当模板参数完全匹配特化类型时,编译器将优先使用该特化版本

// 主模板
template <typename T>
class TypeInfo {
public:static const char* name() { return "unknown"; }
};// 完全特化
template <>
class TypeInfo<int> {
public:static const char* name() { return "int"; }
};template <>
class TypeInfo<std::string> {
public:static const char* name() { return "string"; }
};

2.偏特化

偏特化允许模板对部分参数进行特定类型的处理,同时保持其他参数的通用性

// 主模板
template <typename T, typename U>
class Pair {T first;U second;
public:Pair(const T& t, const U& u) : first(t), second(u) {}void print() { std::cout << first << ", " << second << std::endl; }
};// 指针类型的偏特化
template <typename T, typename U>
class Pair<T*, U*> {T* first;U* second;
public:Pair(T* t, U* u) : first(t), second(u) {}void print() { std::cout << *first << ", " << *second << std::endl; }
};

2.可变参数模板

可变参数模板允许模板定义中包含任意数量的模板参数。主要通过以下方式实现:

  • 参数包(Parameter Pack):用 … 表示的一组参数
  • 包展开(Pack Expansion):将参数包展开成单独的参数

1.函数模板实例

// 递归终止函数 - 处理参数包为空的情况
void print1() {std::cout << std::endl;
}// 可变参数模板函数
template<typename T, typename... Args>  // Args是一个模板参数包
void print1(T first, Args... args) {     // args是一个函数参数包std::cout << first << " ";          // 处理第一个参数print(args...);                     // 递归处理剩余参数
}// 使用折叠表达式
template <typename... Args>
void print2(const Args&... args) {// 使用左折叠展开参数包,并在每个参数之后输出一个空格((std::cout << args << " "), ...);std::cout << std::endl;
}// 使用示例
print(1, "hello", 3.14, 'c');  // 可以接受任意数量和类型的参数

2.参数包的使用方法

template<typename... Args>
void example(Args... args) {// 获取参数包中的参数数量constexpr size_t size = sizeof...(Args);// 展开参数包的几种方式// 1. 直接展开func(args...);  // 展开成 func(arg1, arg2, arg3)// 2. 使用初始化列表展开int arr[] = {(std::cout << args << " ", 0)...};// 3. 折叠表达式(C++17)(std::cout << ... << args);
}

3.类模板中的可变参数

// 基本的Tuple声明
template<typename... Types>
class Tuple;// 递归特化版本
template<typename First, typename... Rest>
class Tuple<First, Rest...> : private Tuple<Rest...> {First first;  // 存储当前层级的值
public:// 构造函数接收当前值和剩余参数Tuple(First f, Rest... rest) : Tuple<Rest...>(rest...), // 递归构造基类first(f) {}              // 初始化当前值
};// 特化的终止条件
template<>
class Tuple<> {};  // 空tuple作为递归终点// 使用示例
Tuple<int, string, double> t(1, "hello", 3.14);

3.模板折叠

折叠表达式的引入显著简化了处理参数包的过程。它们允许开发者直接对参数包应用操作符,而无需手动展开或递归处理参数。这不仅使代码更加简洁,还提高了可读性和可维护性

1.一元折叠

// 一元左折叠
template<typename... Args>
void print_left_init(Args... args) {(... + args);  // ((arg1 + arg2) + arg3) + arg4
}// 一元右折叠
template<typename... Args>
void print_right_init(Args... args) {(args + ...);  // (arg1 + (arg2 + (arg3 + arg4)))
}

2.二元折叠


// 二元左折叠
template<typename... Args>
void print_left_init(Args... args) {(0 + ... + args);  // ((0 + arg1) + arg2) + arg3
}// 二元右折叠
template<typename... Args>
void print_right_init(Args... args) {(args + ... + 0);  // (arg1 + (arg2 + (arg3 + 0)))
}

3.其他实例

template<typename T, typename... Args>
void pushToVector(std::vector<T>& v, Args... args) {(v.push_back(args), ...);  // 逗号表达式折叠
}// 使用示例
std::vector<int> vec;
pushToVector(vec, 1, 2, 3, 4);  // vec: [1,2,3,4]

4.空参数包处理

template<typename... Args>
auto sum(Args... args) {return (args + ...);  // 空参数包会导致编译错误return (args + ... + 0);  // 安全:空参数包返回0
}

4.常用技巧

1.SFINAE(Substitution Failure Is Not An Error)

SFINAE (Substitution Failure Is Not An Error) – 替换失败不是错误是C++模板编程中的一个重要概念,它允许在模板实例化失败时继续查找其他可能的重载

1.简单示例

// 检查类型是否有size()成员函数
template<typename T>
struct has_size {
private:// 测试size()是否存在template<typename U>static auto test(int) -> decltype(std::declval<U>().size(), std::true_type{});// 后备方案template<typename>static std::false_type test(...);public:static constexpr bool value = decltype(test<T>(0))::value;
};

2.enable_if

// 只接受整数类型的函数
template<typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
is_odd(T i) {return bool(i % 2);
}// 只接受浮点类型的函数
template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
is_odd(T x) {return false;  // 浮点数没有奇偶性
}

3.if_constexpr(C++17)

template<typename T>
void process(const T& value) {if constexpr (std::is_integral_v<T>) {// 整数类型处理std::cout << "整数: " << value << std::endl;} else if constexpr (std::is_floating_point_v<T>) {// 浮点类型处理std::cout << "浮点数: " << value << std::endl;} else {// 其他类型std::cout << "其他类型" << std::endl;}
}

4.concepts(C++20)

// 定义一个 concept:要求类型必须是可输出到 std::ostream
template<typename T>
concept Printable = requires(T x) {{ std::cout << x } -> std::same_as<std::ostream&>;
};template<Printable T>
void print(const T& value) {std::cout << value << std::endl;
}

5.总结

SFINAE作为C++模板编程中的一项强大功能,通过在模板实例化过程中允许替换失败而不报错,实现了基于类型特性的编程。然而,SFINAE的语法复杂且难以维护,现代C++引入的新特性如概念等在某些情况下已经能够更简洁地实现类似的功能。尽管如此,理解SFINAE的工作机制依然对于掌握高级模板技术和阅读老旧代码具有重要意义

2.模板元编程

模板元编程是在编译期进行的计算和类型操作,主要用于:

  1. 编译期计算
  2. 类型转换和判断
  3. 代码生成

1.计算示例

// 1.编译期计算阶乘
template<unsigned N>
struct Factorial {static constexpr unsigned value = N * Factorial<N-1>::value;
};
// 递归终止
template<>
struct Factorial<0> {static constexpr unsigned value = 1;
};
// 使用
constexpr auto result = Factorial<5>::value;  // 5! = 120// 2.斐波那契数列
template<unsigned N>
struct Fibonacci {static constexpr unsigned value = Fibonacci<N-1>::value + Fibonacci<N-2>::value;
};
template<>
struct Fibonacci<0> {static constexpr unsigned value = 0;
};
template<>
struct Fibonacci<1> {static constexpr unsigned value = 1;
};

2.类型操作

// 1.判断类型是否为指针
template<typename T>
struct IsPointer {static constexpr bool value = false;
};template<typename T>
struct IsPointer<T*> {static constexpr bool value = true;
};// 2.移除引用
template<typename T>
struct RemoveReference {using type = T;
};template<typename T>
struct RemoveReference<T&> {using type = T;
};template<typename T>
struct RemoveReference<T&&> {using type = T;
};

3.注意事项

  1. 编译时间:复杂的模板元编程会增加编译时间、应适度使用,避免过度复杂化
  2. 可读性:模板元编程代码往往难以理解、需要良好的文档和注释
  3. 调试难度:编译期错误信息复杂、调试工具支持有限
  4. 维护成本:代码复杂度高、修改需要谨慎

5.其他

本号文章仅为个人收集总结,强烈欢迎大佬与同好指误或讨论 ^^

相关文章:

  • 47-dify案例分享-魔搭+Dify王炸组合!10分钟搭建你的专属 生活小助理
  • 利用【指针引用】对【非空单循环链表】进行删除操作
  • C/C++ | 高频手撕整理(1) —— strstr,memcpy,memmove函数模拟实现
  • ARM架构的微控制器总线矩阵优先级与配置
  • DeepSeek 的长上下文扩展机制
  • 【MCP】详细了解MCP协议:和function call的区别何在?如何使用MCP?
  • 0803分页_加载更多-网络ajax请求2-react-仿低代码平台项目
  • 【多线程】五、线程同步 条件变量
  • 逆向|dy|a_bogus|1.0.1.19-fix.01
  • RK3568 Debian调试记录
  • 基于强化学习的智能交通控制系统设计
  • 基于STM32单片机PWM讲解(HAL库)
  • html css js网页制作成品——HTML+CSS+js美甲店网页设计(5页)附源码
  • pytest 技术总结
  • Windows怎样使用curl下载文件
  • 大模型时代的语言格局演变:为什么是 JavaScript?
  • uml类关系(实现、继承,聚合、组合,依赖、关联)
  • Python并发编程全景解析:多线程、多进程与协程的深度对比
  • 职场十二法则-马方
  • 刚体运动 (位置向量 - 旋转矩阵) 笔记 1.1~1.3 (台大机器人学-林沛群)
  • 《深度参与全球海洋治理的重大科技问题战略研究》一书出版发行
  • 人民日报读者点题:规范涉企执法,怎样防止问题反弹、提振企业信心?
  • 大学2025丨专访北邮校长徐坤:工科教育要真正回归工程本质
  • 以军称若停火谈判无进展,将大幅扩大加沙军事行动
  • 俄罗斯称已收复库尔斯克州
  • 2025年上海空间信息大会举行,重大项目集中签约