C++ 模板

C++ 泛型编程(Generic Programming)是一种编写与具体类型无关的通用代码的范式,核心目标是代码复用类型安全 ,通过模板(Template)机制实现。相比面向对象的 "运行时多态",泛型编程实现 "编译期多态",在保证灵活性的同时避免了虚函数的运行时开销。

1、函数模板

函数模板定义一个通用函数,其参数类型、返回值类型可以是 "类型参数",编译器会根据传入的实参类型自动生成具体版本(模板实例化)。

cpp 复制代码
// 函数模板:交换两个任意类型的值
template <typename T> // 声明类型参数 T(typename 可替换为 class)
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 1, y = 2;
    swap(x, y); // 编译器自动实例化 swap<int>(int&, int&)

    double a = 3.14, b = 2.71;
    swap(a, b); // 实例化 swap<double>(double&, double&)

    string s1 = "hello", s2 = "world";
    swap(s1, s2); // 实例化 swap<string>(string&, string&)
    return 0;
}
  • 类型参数 :用 template <typename T> 声明(typenameclass 等价),T 是 "占位类型",实例化时被具体类型替换。
  • 实例化 :编译器根据实参类型自动生成具体函数(如 swap<int>),这一过程在编译期完成。
  • 多类型参数 :支持多个类型参数,如 template <typename T, typename U>

2、类模板

类模板定义一个通用类,类中的成员变量、成员函数的类型可以参数化,典型例子是标准库中的 vector<T>map<K, V> 等容器。

cpp 复制代码
// 类模板:简单的动态数组
template <typename T>
class Array {
private:
    T* data;
    size_t size;
public:
    // 构造函数
    Array(size_t n) : size(n) {
        data = new T[size];
    }

    // 析构函数
    ~Array() {
        delete[] data;
    }

    // 下标访问
    T& operator[](size_t index) {
        return data[index];
    }

    size_t get_size() const {
        return size;
    }
};

int main() {
    // 实例化 Array<int> 类型
    Array<int> int_arr(3);
    int_arr[0] = 10;
    int_arr[1] = 20;

    // 实例化 Array<string> 类型
    Array<string> str_arr(2);
    str_arr[0] = "hello";
    str_arr[1] = "generic";
    return 0;
}
  • 实例化方式 :类模板必须显式指定类型参数(如 Array<int>),编译器不会自动推导。

  • 成员函数 :类模板的成员函数可以在类内定义,也可以在类外定义(需用 template <typename T> 修饰)。

    cpp 复制代码
    // 类外定义成员函数
    template <typename T>
    T& Array<T>::operator[](size_t index) {
        return data[index];
    }

3、模板特化

模板特化是对特定类型提供定制化实现,当通用模板不适合某类类型时(如指针、数组),可通过特化覆盖默认行为。分为全特化偏特化

3.1 全特化

对模板的所有类型参数指定具体类型,完全覆盖通用模板。

函数模板全特化

cpp 复制代码
// 通用模板
template <typename T>
void print(T value) {
    cout << "Generic: " << value << endl;
}

// 对 const char* 特化(字符串字面量类型)
template <>
void print<const char*>(const char* value) {
    cout << "Specialized for string: " << value << endl;
}

int main() {
    print(100); // 调用通用模板:Generic: 100
    print("hello"); // 调用特化版本:Specialized for string: hello
    return 0;
}

类模板全特化

cpp 复制代码
// 通用模板
template <typename T>
class Container {
public:
    void store(T value) {
        cout << "Storing generic type: " << value << endl;
    }
};

// 对 int 类型全特化
template <>
class Container<int> {
public:
    void store(int value) {
        cout << "Storing integer: " << value << " (specialized)" << endl;
    }
};

int main() {
    Container<double> d;
    d.store(3.14); // 通用版本:Storing generic type: 3.14

    Container<int> i;
    i.store(10); // 特化版本:Storing integer: 10 (specialized)
    return 0;
}

3.2 偏特化

对模板的部分类型参数指定具体类型(适用于多参数模板),或对类型参数添加约束(如指针、引用)。

类模板偏特化(多参数模板)

cpp 复制代码
// 通用模板(两个类型参数)
template <typename T, typename U>
class Pair {
public:
    T first;
    U second;
    Pair(T f, U s) : first(f), second(s) {}
};

// 偏特化:第一个参数为 int
template <typename U>
class Pair<int, U> {
public:
    int first;
    U second;
    Pair(int f, U s) : first(f), second(s) {
        cout << "Partial specialization (first is int): " << f << ", " << s << endl;
    }
};

int main() {
    Pair<double, string> p1(3.14, "pi"); // 通用版本
    Pair<int, string> p2(10, "hello"); // 偏特化版本:输出 Partial specialization...
    return 0;
}

类模板偏特化(指针类型约束)

cpp 复制代码
// 通用模板
template <typename T>
class Handler {
public:
    void process(T value) {
        cout << "Processing value: " << value << endl;
    }
};

// 偏特化:T 为指针类型
template <typename T>
class Handler<T*> {
public:
    void process(T* value) {
        if (value) {
            cout << "Processing pointer: " << *value << endl;
        } else {
            cout << "Null pointer" << endl;
        }
    }
};

int main() {
    Handler<int> h1;
    h1.process(100); // 通用版本:Processing value: 100

    int x = 200;
    Handler<int*> h2;
    h2.process(&x); // 偏特化版本:Processing pointer: 200
    return 0;
}

4、非类型模板参数

模板参数不仅可以是类型(typename T),还可以是常量表达式(如整数、指针、引用),称为非类型参数。

固定大小的数组模板

cpp 复制代码
// 非类型参数 N 表示数组大小(编译期常量)
template <typename T, size_t N>
class FixedArray {
private:
    T data[N]; // 编译期确定大小,无需动态内存
public:
    T& operator[](size_t index) {
        return data[index];
    }
    size_t size() const { return N; }
};

int main() {
    FixedArray<int, 5> arr; // 大小为 5 的 int 数组(编译期固定)
    for (size_t i = 0; i < arr.size(); ++i) {
        arr[i] = i * 10;
    }
    return 0;
}
  • 约束 :非类型参数必须是编译期可确定的常量 (如字面量、constexpr 变量)。
  • 类型限制 :通常为整数类型(intsize_t)、指针或引用(指向全局对象 / 函数)。

5、模板的编译与实例化

模板的编译机制与普通函数 / 类不同,核心是 "延迟实例化 " 和 "两阶段查找"。

5.1 延迟实例化

编译器仅在模板被使用时(如调用函数模板、创建类模板对象)才生成具体代码(实例化),未使用的模板不会生成代码。

cpp 复制代码
template <typename T>
void func(T x) {
    // 即使 T 不支持 operator++,只要不调用 func,就不会报错
    x++; 
}

int main() {
    // func("hello"); // 若调用,string 不支持 ++,编译报错
    return 0;
}

5.2 两阶段查找

模板编译分为两个阶段:

  1. 第一阶段:解析模板本身(不依赖模板参数的部分),检查语法错误(如缺少分号)。
  2. 第二阶段 :实例化时(替换类型参数后),检查依赖模板参数的部分(如 x++ 是否合法)。

意义:提前发现非依赖于类型参数的错误,提高编译效率。

5.3 模板的声明与定义

模板的定义必须在头文件中(或包含在所有使用它的源文件中),不能像普通函数那样 "头文件声明,源文件定义"。

原因 :实例化时编译器需要看到完整的模板定义才能生成具体代码,若定义在源文件(.cpp)中,其他文件无法访问,会导致链接错误(undefined reference)。

6、C++11 后的泛型增强特性

6.1 autodecltype

  • auto:自动推导变量类型(依赖模板类型推导规则),简化泛型代码。

  • decltype:推导表达式类型,用于声明与表达式类型一致的变量或函数返回值。

    cpp 复制代码
    template <typename T, typename U>
    auto add(T a, U b) -> decltype(a + b) { // 尾置返回类型,依赖 a + b 的类型
        return a + b;
    }
    
    int main() {
        auto result = add(3, 4.5); // result 类型为 double(3 + 4.5 = 7.5)
        return 0;
    }

6.2 模板别名

using 定义模板的别名,简化复杂模板类型。

cpp 复制代码
template <typename T>
using Vec = std::vector<T>; // Vec<int> 等价于 vector<int>

Vec<int> nums; // 简化写法

// 结合非类型参数
template <typename T, size_t N>
using Arr = std::array<T, N>;
Arr<double, 10> data; // 等价于 array<double, 10>

6.3 变参模板

支持参数数量可变的模板(如 printf 风格的函数),通过递归展开参数包实现。

打印任意数量的参数

cpp 复制代码
// 递归终止函数(无参数)
void print() {}

// 变参模板:展开第一个参数,递归处理剩余参数
template <typename T, typename... Args>
void print(T first, Args... rest) {
    cout << first << " ";
    print(rest...); // 递归展开剩余参数
}

int main() {
    print(1, "hello", 3.14, 'a'); // 输出:1 hello 3.14 a
    return 0;
}

关键点Args... 称为 "参数包",rest... 用于展开参数包。

7、常见问题

  1. 什么是泛型编程?C++ 如何实现泛型编程?

    泛型编程是一种编写与具体类型无关的通用代码的范式,核心目标是代码复用和类型安全,在编译期根据实际类型生成具体代码。

    C++ 主要通过模板(Templates) 实现泛型编程,包括:

    • 函数模板:定义通用函数(如 swap<T>),编译器根据实参类型生成具体函数。
    • 类模板:定义通用类(如 vector<T>),通过显式指定类型参数实例化。
  2. 模板特化的作用是什么?全特化与偏特化的区别?

    模板特化的作用是为特定类型提供定制化实现,当通用模板对某些类型(如指针、字符串)不适用时,通过特化覆盖默认行为。

    区别:

    • 全特化 :对模板的所有类型参数 指定具体类型(如 template <> class Container<int>),完全替换通用模板。
    • 偏特化 :对模板的部分类型参数指定具体类型(如多参数模板中固定一个参数),或对类型参数添加约束(如指针、引用),仅覆盖符合条件的实例。
  3. 为什么模板的定义通常放在头文件中?

    因为模板采用 "延迟实例化" 机制:编译器在实例化模板(生成具体类型代码)时,需要看到完整的模板定义(而非仅声明)。

    若模板定义放在源文件(.cpp)中,其他文件包含头文件时只能看到声明,无法实例化模板,会导致链接错误(undefined reference)。因此,模板的定义通常与声明一起放在头文件中。

  4. 模板实例化会导致 "代码膨胀" 吗?如何避免?

    会。模板为每个实例化类型生成独立的代码(如 vector<int>vector<double> 是两个不同的类,生成两套代码),过多实例化会导致目标文件变大(代码膨胀)。

  5. 如何在泛型编程中实现策略模式?

    cpp 复制代码
    // 策略接口作为模板参数
    template<typename SortingStrategy>
    class Sorter {
    public:
        void sort(Container& data) {
            SortingStrategy::sort(data);
        }
    };
    
    // 各种策略实现
    struct QuickSort {
        template<typename Container>
        static void sort(Container& data) {
            // 快速排序实现
        }
    };
    
    struct MergeSort {
        template<typename Container>
        static void sort(Container& data) {
            // 归并排序实现
        }
    };
    
    // 使用:编译期策略选择
    Sorter<QuickSort> quickSorter;
    Sorter<MergeSort> mergeSorter;
相关推荐
感哥6 小时前
C++ 多重继承
c++
博笙困了6 小时前
C++提高编程 4.0
c++
扑克中的黑桃A6 小时前
[C语言]第三章-数据类型&变量
c++
感哥7 小时前
C++ std::string
c++
感哥1 天前
C++ 面向对象
c++
沐怡旸1 天前
【底层机制】std::shared_ptr解决的痛点?是什么?如何实现?如何正确用?
c++·面试
感哥1 天前
C++ STL 常用算法
c++
saltymilk2 天前
C++ 模板参数推导问题小记(模板类的模板构造函数)
c++·模板元编程
感哥2 天前
C++ lambda 匿名函数
c++