c++模板精讲与拓展

什么是泛型编程?

泛型编程是一种编程范式,核心思想是编写与具体数据类型无关的通用代码,从而提高代码的复用性和灵活性。允许开发者定义可适应多种数据类型的函数,类或接口,无需为每种类型重复编写相似的代码。

什么是C++模板?

实现泛型编程的机制,可以让开发者编写与数据类型无关的代码,通过编译器自动生成具体类型代码。

作用

  • 代码复用
  • 类型安全:编译时进行类型检查
  • 性能优化:编译生成的代码具体类型化,无运行时类型判断开销

C++模板与泛型编程的关系

C++模板是泛型编程的子集,模板是C++实现泛型编程的核心机制;

泛型编程是一种编程范式,强调与类型无关的通用代码;

而C++模板只是用来实现泛型编程的一种工具,其他语言也可以通过不同形式去实现泛型编程。

语言机制核心特点
C++模板编译时实例化代码,支持元编程,类型完全开放(可能会导致代码膨胀)
Java类型擦除泛型信息在运行时擦除,兼容旧版本,类型约束较弱(需要显示强制转化)
C#运行时保留类型信息泛型代码在运行时保留类型参数,性能更优
Rust零成本抽象泛型编译时静态派发,通过trait约束类型

模板类型与使用

函数模板

以 通用交换函数 举例

#include <iostream>
#include <string>

// 传统方式:为每种类型单独写交换函数
void swapInt(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

void swapDouble(double& a, double& b) {
    double temp = a;
    a = b;
    b = temp;
}

// 模板方式:一个函数处理所有类型
template<typename T>
void mySwap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

int main() {
    // 测试整型交换
    int x = 10, y = 20;
    mySwap(x, y); // 编译器自动推导类型为int
    std::cout << "x=" << x << ", y=" << y << std::endl;

    // 测试浮点型交换
    double m = 3.14, n = 2.71;
    mySwap<double>(m, n); // 显式指定类型
    std::cout << "m=" << m << ", n=" << n << std::endl;

    // 测试字符串交换
    std::string s1 = "Hello", s2 = "World";
    mySwap(s1, s2);
    std::cout << "s1=" << s1 << ", s2=" << s2 << std::endl;

    return 0;
}

类模板

以自定义数组为例

#include <iostream>
#include <stdexcept> // 用于异常处理

template<typename T>
class MyArray {
private:
    T* data;         // 存储数据的指针
    size_t capacity; // 数组容量
    size_t size;     // 当前元素数量

public:
    // 构造函数
    MyArray() : data(new T[5]), capacity(5), size(0) {}

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

    // 添加元素
    void push_back(const T& value) {
        if(size >= capacity) {
            // 容量翻倍扩容
            capacity *= 2;
            T* newData = new T[capacity];
            for(size_t i = 0; i < size; ++i) {
                newData[i] = data[i];
            }
            delete[] data;
            data = newData;
        }
        data[size++] = value;
    }

    // 获取元素
    T& operator[](size_t index) {
        if(index >= size) {
            throw std::out_of_range("Index out of range");
        }
        return data[index];
    }

    // 获取数组大小
    size_t getSize() const {
        return size;
    }

    // 打印数组内容
    void print() const {
        std::cout << "[ ";
        for(size_t i = 0; i < size; ++i) {
            std::cout << data[i] << " ";
        }
        std::cout << "]\n";
    }
};

int main() {
    // 创建int类型的数组
    MyArray<int> intArray;
    intArray.push_back(10);
    intArray.push_back(20);
    intArray.push_back(30);
    std::cout << "Int array: ";
    intArray.print(); // 输出: [ 10 20 30 ]

    // 创建string类型的数组
    MyArray<std::string> strArray;
    strArray.push_back("Hello");
    strArray.push_back("Template");
    strArray.push_back("!");
    std::cout << "String array: ";
    strArray.print(); // 输出: [ Hello Template ! ]

    // 测试越界访问
    try {
        std::cout << "Third element: " << intArray[2] << std::endl; // 正常
        std::cout << "Fifth element: " << intArray[4] << std::endl; // 抛异常
    } catch(const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    return 0;
}

可能会有小伙伴们问到为什么不使用继承来代替模板的工作,明明都是对代码的扩展,请继续往下看

继承与模板

对比

特性模板继承
扩展维度数据类型 (T)行为实现
多态类型编译时(静态多态)运行时(动态多态)
代码生成时机编译时实例化运行时通过虚函数派发
约束方式隐式概念/显式断言(requires基类接口(抽象类、纯虚函数)
典型应用泛型算法、容器多态对象、统一接口的多种实现

协同工作

两种技术并非互斥,如 CRTP(Curiously Recurring Template Pattern,奇异递归模板模式)同时使用继承和模板。

CRTP

核心思想基类将自己定义为模板类,并以派生类作为模板参数,从而在基类中通过静态类型转换(static_cast)直接访问派生类的成员,实现编译时多态(静态多态)。

  • 基类模板化:基类是一个模板类,其模板参数是派生类自身
  • 静态多态:基类通过 static_castthis 指针转换为派生类指针,直接调用派生类的成员方法,无需虚函数和运行时开销

基本结构:

template <typename Derived>  // 基类是模板类,模板参数为派生类
class Base {
public:
    void commonOperation() {
        // 通过 static_cast 将基类指针转换为派生类指针,调用派生类方法
        static_cast<Derived*>(this)->customStep();
    }
};

// 派生类继承基类时,将自己作为模板参数传递给基类,这里已经隐式生成了一个Base<MyClass>
class MyClass : public Base<MyClass> {
public:
    void customStep() {
        // 派生类实现具体逻辑
        std::cout << "MyClass customStep" << std::endl;
    }
};
CRTP 的典型应用

运算符重载(Boost.Operators)

Boost 库使用 CRTP 简化运算符重载。例如,实现 operator+ 时自动生成 operator+=

template <typename Derived>
class Addable {
public:
    Derived operator+(const Derived& other) const {
        Derived result = static_cast<const Derived&>(*this);
        result += other;
        return result;
    }
};

class Vec3 : public Addable<Vec3> {
public:
    Vec3(int x, int y, int z) : x(x), y(y), z(z) {}
    Vec3& operator+=(const Vec3& other) {
        x += other.x;
        y += other.y;
        z += other.z;
        return *this;
    }
    int x, y, z;
};

// 使用
Vec3 a(1, 2, 3), b(4, 5, 6);
Vec3 c = a + b;  // 调用 Addable<Vec3>::operator+,内部使用 Vec3::operator+=
  1. 单例模式(编译时单例)

通过 CRTP 实现通用的单例基类。

template <typename T>
class Singleton {
public:
    static T& getInstance() {
        static T instance;
        return instance;
    }
protected:
    Singleton() = default;
    ~Singleton() = default;
};

class Logger : public Singleton<Logger> {
    friend class Singleton<Logger>;  // 允许基类构造 Logger
public:
    void log(const std::string& message) { /* ... */ }
};

// 使用
Logger::getInstance().log("Hello CRTP");
CRTP 的优缺点
优点缺点
消除虚函数调用开销(静态多态)代码可读性降低(基类与派生类紧密耦合)
编译时类型安全(无运行时错误)需手动管理类型转换(易出错)
可复用通用逻辑(如计数器、运算符重载)不适用于需要运行时动态派发的场景

模板重载 & 模板特化

模板重载

// 函数模板重载示例:处理普通类型和指针类型
template <typename T>
void process(T value) { /* 通用实现 */ }

template <typename T>
void process(T* value) { /* 指针特化逻辑 */ }

模板特化:分为全部特化和部分特化

模板全特化

// 类模板全特化示例:针对 int 类型的特殊实现
template <typename T>
class Container { /* 通用实现 */ };

template <>
class Container<int> { /* int 类型的特化实现 */ }; [9](@ref)

// 函数模板全特化示例:针对 const char* 类型
template <>
const char* max<const char*>(const char* a, const char* b) {
    return strcmp(a, b) > 0 ? a : b;
}

模板部分特化

// 通用模板
template <typename T1, typename T2, typename T3>
class TCP { /* 通用实现 */ };

// 部分特化:仅第一个参数为 int,第三个为 double
template <typename U>
class TCP<int, U, double> { /* 针对 int 和 double 的优化实现 */ };
特性模板重载模板特例化
本质多个独立模板的共存,通过参数差异区分单一模板的特定类型扩展实现
适用范围函数模板、类模板函数全特化、类全特化/部分特化
参数匹配基于参数类型和数量的最佳匹配严格匹配特定类型或类型组合
实现灵活性可定义完全不同的参数列表必须保持原模板参数结构
优先级普通函数 > 模板函数特例化版本 > 通用模板
典型应用场景处理不同类型或数量的参数(如指针、数组)优化特定类型的性能或功能(如字符串处理)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值