专业的编程技术博客社区

网站首页 > 博客文章 正文

C++11+ 泛型编程(模板)

baijin 2025-08-03 04:03:42 博客文章 3 ℃ 0 评论

之前研究了 ROS2(Jazzy)机器人开发系统,并将官网中比较重要的教程和概念,按照自己的学习顺序翻译成了中文,进行了整理和记录。到目前为止,已经整理了20多篇文章。如果想回顾之前的内容,可以查阅主页中 ROS2(Jazzy)相关文章。

在研究 ROS2 的过程中,发现它使用了不少 C++11 的新特性。因此,开启了现代 C++11+ 新特性的总结系列。目前已经完成了以下几篇:

  1. C++11移动语义‘偷梁换柱’实战
  2. C++11 Lambda 表达式 以及 std::function和std::bind
  3. C++11 智能指针:unique_ptr、shared_ptr 和 weak_ptr
  4. C++11 的线程管理(std::thread)
  5. C++11 原子操作 std::atomic
  6. C++11 同步机制:互斥锁和条件变量

这篇是关于 C++ 泛型编程(模板)的总结。

C++ 泛型编程(Generic Programming)是一种编程范式,它允许你编写独立于具体数据类型的代码,通过参数化类型(Type Parameterization)来实现代码复用。这种范式的核心是模板(Templates),它是 C++ 标准模板库(STL)的基石。在这里的参数化意思是数据类型在编译时不确定,在运行时以参数的形式传给模类或函数。

一、泛型编程的核心思想

“编写与类型无关的通用算法”

过去传统编程时,需要为每个数据类型编写特定的代码和算法,比如,分别为 intdouble 编写的排序函数。 而在泛型编程中,你可以编写一次代码,只要这些类型满足特定的接口要求,就能用于多种数据类型。

二、模板(Templates):泛型编程的基石

模板是 C++ 实现泛型编程的核心机制,允许你编写与类型无关的代码,实现“一次编写,多类型复用”。

1. 函数模板(Function Templates)

1)基础语法

template <typename T>  // 声明模板参数 T
T max(T a, T b) {       // 泛型函数:返回两个值中的较大值
    return (a > b) ? a : b;
}
  • template <typename T>:声明一个模板参数 Ttypename 也可用 class 替代
  • 函数体内 T 可作为普通类型使用

2) 调用方式

int x = max(1, 2);           // 自动推导 T 为 int
double y = max(3.14, 2.71);  // 自动推导 T 为 double

// 显式指定模板参数
int z = max<int>(1, 2.5);    // 将 2.5 截断为 int 类型

3)多模板参数

template <typename T, typename U>
auto add(T a, U b) {         // C++14 自动推导返回类型
    return a + b;
}

// 使用
auto result = add(1, 3.14);  // T=int, U=double, 返回 double 类型

2. 类模板(Class Templates)

1) 基础语法

template <typename T>
class Vector {
private:
    T* data;
    size_t size;
public:
    Vector(size_t n) : data(new T[n]), size(n) {}
    ~Vector() { delete[] data; }
    
    T& operator[](size_t i) { return data[i]; }
    const T& operator[](size_t i) const { return data[i]; }
};

2) 实例化与使用

Vector<int> intVec(5);      // 存储 int 的向量
Vector<std::string> strVec(3);  // 存储 string 的向量

intVec[0] = 100;
strVec[1] = "hello";

3) 类模板的成员函数

成员函数可在类内定义,也可在类外定义(需显式指定模板参数):

template <typename T>
class Vector {
    // ... 类定义同上 ...
    
    // 类外定义的成员函数声明
    void resize(size_t newSize);
};

// 类外定义成员函数
template <typename T>
void Vector<T>::resize(size_t newSize) {
    // 实现略
}

3. 非类型模板参数(Non-Type Template Parameters)

模板参数可以是类型,也可以是常量值,非类型的模板参数支持:整数、枚举、指针、引用等

template <typename T, size_t N>
class Array {
private:
    T data[N];  // 使用常量值 N 作为数组大小
public:
    size_t size() const { return N; }
};

// 使用
Array<int, 5> arr;  // 创建包含 5 个 int 的数组

4. 模板特化(Template Specialization)

为特定类型提供定制的模板代码实现:

// 通用模板
template <typename T>
struct IsPointer {
    static constexpr bool value = false;
};

// 特化版本:针对指针类型
template <typename T>
struct IsPointer<T*> {
    static constexpr bool value = true;
};

// 使用
IsPointer<int>::value;      // false
IsPointer<int*>::value;     // true

这里还用到了 constexpr 关键字,它允许在编译期执行计算,将运行时开销转移到编译时,从而提高程序性能并增强类型安全性。 它可以修饰:

  • 变量:确保变量的值在编译期确定
  • 函数:允许函数在编译期调用(如果参数是编译期常量)
  • 构造函数:允许对象在编译期构造

5. 部分特化(Partial Specialization)

对模板参数的部分组合进行特化:

// 原始模板
template <typename T, typename U>
struct Pair {};

// 部分特化:当 U 是指针类型时
template <typename T, typename U>
struct Pair<T, U*> {};

6. 模板默认参数

template <typename T = int, size_t N = 10>
class Stack {
    // ... 实现略 ...
};

// 使用
Stack<> s1;          // 默认 T=int, N=10
Stack<double, 20> s2;  // 指定 T=double, N=20

7. 可变参数模板(Variadic Templates)

处理任意数量和类型的参数:

// 终止函数(递归终点)
void print() {
    std::cout << "\n";
}

// 可变参数模板函数
template <typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first;
    if constexpr (sizeof...(args) > 0) {  // C++17
        std::cout << ", ";
    }
    print(args...);  // 递归展开参数包
}

// 使用
print(1, 2.5, "hello");  // 输出: 1, 2.5, hello

8. 模板元编程(Template Metaprogramming)

元编程(Metaprogramming)是一种编程技术,允许程序在编译时进行计算、生成代码或操作类型信息,具体的说就是程序在编译时处理类型和常量表达式,生成优化后的代码,而不是在运行时执行这些操作。C++ 的元编程主要通过模板(Templates)和类型系统实现,是泛型编程的高级应用。

你可以用元编程在编译期执行计算阶乘:

// 编译期计算阶乘
template <int N>
struct Factorial {
    static constexpr int value = N * Factorial<N-1>::value;
};

template <>
struct Factorial<0> {
    static constexpr int value = 1;
};

// 使用
constexpr int x = Factorial<5>::value;  // 编译期计算为 120

或者使用 constexpr 关键字在编译时执行函数。 比如编译时斐波那契数列

运行
constexpr int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n-1) + fibonacci(n-2);
}

// 使用:constexpr int x = fibonacci(10); // 编译时计算为55

元编程是 C++ 中强大但高级的技术,但需要谨慎使用,避免过度复杂。随着 C++ 标准的演进(如 Concepts 和编译时反射),元编程的语法和易用性正在不断改进。

需要注意元编程技术与泛型编程的区别:

  • 泛型编程是一种编写独立于具体数据类型的通用代码的编程范式,通过参数化类型(如模板)实现 “一次编写,多种类型复用”,它的目标是代码复用,让同一套逻辑(如容器、算法)能适配任意数据类型(int、string、自定义类等),同时保证类型安全和运行效率。它执行时机是运行时。
  • 元编程是是一种以代码作为数据,在编译期执行计算或生成代码的编程范式。简单说,是“用代码生成代码” 或 “在编译时做计算”。它的核心目标:编译期优化(如提前计算常量、消除分支)、类型操纵(如类型检查、自动生成适配特定类型的代码),最终减少运行时开销或实现复杂的类型安全逻辑。它的执行时机是编译时。

元编程主要依赖了以下关键技术:

1) 模板特化(Template Specialization)

针对特定类型提供定制实现。

示例:类型检查

template <typename T>
struct is_integral {
    static constexpr bool value = false;
};

template <>
struct is_integral<int> {
    static constexpr bool value = true;
};

template <>
struct is_integral<char> {
    static constexpr bool value = true;
};

// 使用:is_integral<double>::value  // false

2) 递归模板实例化

通过模板递归实现编译时循环。

示例:编译时数组求和

template <int... Values>
struct Sum;

template <>
struct Sum<> {
    static constexpr int value = 0;
};

template <int Head, int... Tail>
struct Sum<Head, Tail...> {
    static constexpr int value = Head + Sum<Tail...>::value;
};

// 使用:Sum<1, 2, 3>::value  // 编译时计算为6

3) 类型萃取(Type Traits)

在编译时查询或转换类型属性。

示例:获取函数返回类型

template <typename Func>
struct function_traits;

template <typename R, typename... Args>
struct function_traits<R(Args...)> {
    using return_type = R;
};

// 使用:function_traits<int(double, char)>::return_type  // int

4) 编译时条件判断(C++11+)

使用 std::conditionalif constexpr(C++17+)。

示例:条件选择类型

template <bool Condition, typename T, typename U>
using conditional_t = typename std::conditional<Condition, T, U>::type;

// 使用:conditional_t<true, int, double>  // int
#include <type_traits>

// 元编程:编译期判断类型是否为指针
template <typename T>
void print_type_info() {
    if constexpr (std::is_pointer_v<T>) {  // constexpr if在编译期分支选择, 通过 “类型 traits”(如std::is_pointer<T>)判断类型属性
        std::cout << "Type is a pointer\n";
    } else {
        std::cout << "Type is not a pointer\n";
    }
}

int main() {
    print_type_info<int>();    // 编译期确定输出“not a pointer”
    print_type_info<int*>();   // 编译期确定输出“is a pointer”
    return 0;
}

9. C++20 新特性:概念(Concepts)

概念(Concepts)可以用于约束模板参数的类型要求:

// 定义概念:要求类型支持加法
template <typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::same_as<T>;  // 要求 a+b 返回 T 类型
};

// 使用概念约束的函数模板
template <Addable T>
T add(T a, T b) {
    return a + b;
}

三、标准模板库(STL):泛型编程的典范

STL 是 C++ 泛型编程的集大成者,建议对 STL 的源码进行剖析,深入理解容器(vectormapunordered_map)的实现原理,迭代器失效机制及空间配置器设计。然后了解算法(如sortfind)的底层优化策略,并结合C++20 Ranges库实现链式调用。

这里简要但要一下 STL 四大组件:

1. 容器(Containers)

存储数据的泛型类,如:

  • 序列容器vectorlistdeque
  • 关联容器setmapunordered_setunordered_map
  • 适配器stackqueuepriority_queue

2. 算法(Algorithms)

操作容器的泛型函数,如:

  • sortfindtransformaccumulate
  • 位于 <algorithm><numeric> 头文件中

3. 迭代器(Iterators)

容器和算法之间的接口,如:

  • begin()end()rbegin()rend()
  • 特殊迭代器:back_inserteristream_iterator

4. 函数对象(Function Objects)

可调用对象,用于自定义算法行为,如:

  • std::lessstd::greater
  • Lambda 表达式(C++11+)

示例:使用 STL 算法和容器

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> v = {3, 1, 4, 1, 5, 9};
    
    // 排序
    std::sort(v.begin(), v.end());  // 使用随机访问迭代器
    
    // 查找
    auto it = std::find(v.begin(), v.end(), 5);
    
    // 使用 Lambda 表达式过滤元素
    std::for_each(v.begin(), v.end(), [](int x) {
        std::cout << x << " ";
    });
    
    return 0;
}

四、迭代器(Iterators):泛型算法的接口

这里将 STL 中迭代器单列出来说一下,迭代器是一种抽象的指针,用于遍历容器中的元素,是连接算法和容器的桥梁。

1. 迭代器分类(按功能从弱到强)

  • 输入迭代器(Input Iterator):只读,单向移动(如 istream_iterator
  • 输出迭代器(Output Iterator):只写,单向移动(如 ostream_iterator
  • 前向迭代器(Forward Iterator):可读可写,单向多次移动(如 std::forward_list 的迭代器)
  • 双向迭代器(Bidirectional Iterator):支持双向移动(如 std::list 的迭代器)
  • 随机访问迭代器(Random Access Iterator):支持随机访问(如 std::vector 的迭代器)

2. 泛型算法通过迭代器操作数据

示例:泛型查找算法

template <typename InputIt, typename T>
InputIt find(InputIt first, InputIt last, const T& value) {
    for (; first != last; ++first) {
        if (*first == value) {
            return first;
        }
    }
    return last;
}

// 使用:
std::vector<int> vec = {1, 2, 3, 4};
auto it = find(vec.begin(), vec.end(), 3);  // 找到元素3的迭代器

五、泛型编程的应用场景

  1. 容器和数据结构:如 vectormapqueue
  2. 算法库:如排序、查找、过滤算法
  3. 智能指针:如 std::unique_ptrstd::shared_ptr
  4. 并发编程:如 std::threadstd::future
  5. 元编程:在编译时执行计算(见前文元编程部分)

六、注意事项

  1. 编译时错误:模板错误信息可能晦涩难懂
  2. 代码膨胀:大量模板实例化可能增加可执行文件大小
  3. 过度泛化:避免为了泛型而牺牲代码可读性
  4. 性能权衡:某些情况下,特定类型的实现可能比泛型更高效

总结

泛型编程是 C++ 的核心特性之一,通过模板机制实现了类型无关的代码复用。它是 STL 的基础,使 C++ 能够提供强大而灵活的容器和算法库。掌握泛型编程需要理解模板、迭代器和类型系统的深层交互,以及如何平衡代码的通用性和可读性。随着 C++ 标准的演进(如 Concepts 的引入),泛型编程的表达力和易用性正在不断提升。


作者:小芝,干了二十多年的C++开发。开发过桌面软件,干过古早功能手机游戏开发,做过几个IOS/Android客户端APP。现在对AI开发和机器人开发有兴趣,同时也在了解产品相关知识。若喜欢本文,欢迎点赞、在看、留言交流。

欢迎关注 【智践行】 一起学习机器人开发,发送【C++】获得学习资料。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表