一、C 模板体系与模板特化模板是 C 泛型编程的核心属于编译期多态在编译阶段根据传入的类型生成对应版本的代码实现一套逻辑适配多种数据类型兼顾通用性与运行效率。1.1 基础模板函数模板与类模板函数模板针对函数逻辑的泛型封装编译器根据实参类型自动推导模板参数生成对应类型的函数实例。cpp运行// 通用函数模板定义 templatetypename T // templateclass T 等价typename更推荐 T myMax(T a, T b) { return a b ? a : b; } // 调用示例 int main() { myMax(10, 20); // 自动推导T为int myMax(3.14, 2.71); // 自动推导T为double myMaxint(10, 20L); // 显式指定T为int避免类型不匹配 return 0; }类模板针对类的泛型封装类模板无法自动推导类型必须显式指定模板参数。类模板的成员函数在类外实现时必须再次声明模板参数。cpp运行templatetypename T class Array { private: T* data; int size; public: Array(int n) : size(n) { data new T[n]; } // 类内声明、类外实现的成员函数 T operator[](int index); ~Array() { delete[] data; } }; // 类外实现成员函数必须带模板声明 templatetypename T T ArrayT::operator[](int index) { return data[index]; }注意模板的声明与定义通常必须放在头文件中。因为模板实例化发生在编译期编译器必须看到完整的模板定义才能生成对应类型的代码否则会出现链接错误。1.2 模板特化特定类型的定制实现当通用模板逻辑对某些特殊类型不适用时可以通过模板特化为该类型单独编写实现逻辑。特化版本优先级高于通用模板。类模板特化类模板支持全特化和偏特化两种形式。1全特化所有模板参数都指定具体类型cpp运行// 通用类模板 templatetypename T class TypeInfo { public: static void print() { std::cout 通用类型 std::endl; } }; // 全特化针对int类型的定制实现 template class TypeInfoint { public: static void print() { std::cout 整型4字节 std::endl; } }; // 调用TypeInfodouble::print() → 通用版本TypeInfoint::print() → 特化版本2偏特化仅指定部分模板参数或针对某一类类型做范围特化cpp运行// 双参数通用模板 templatetypename T1, typename T2 class Pair { public: Pair() { std::cout 通用双参数模板 std::endl; } }; // 偏特化1固定第二个参数为int templatetypename T1 class PairT1, int { public: Pair() { std::cout 偏特化第二个参数为int std::endl; } }; // 偏特化2针对所有指针类型 templatetypename T class PairT*, T* { public: Pair() { std::cout 偏特化双指针类型 std::endl; } };匹配规则调用时优先匹配最特化的版本全特化 偏特化 通用模板。函数模板特化函数模板仅支持全特化不支持偏特化。如果需要类似偏特化的效果应使用函数重载替代。cpp运行// 通用函数模板 templatetypename T bool isEqual(T a, T b) { return a b; } // 函数模板全特化针对const char*字符串的定制比较 template bool isEqualconst char*(const char* a, const char* b) { return strcmp(a, b) 0; }二、RAII 资源管理核心思想RAIIResource Acquisition Is Initialization资源获取即初始化是 C 语言最重要的设计思想之一本质是利用栈对象的自动生命周期绑定资源的申请与释放从机制上避免资源泄漏同时保证异常安全。2.1 核心逻辑资源获取与对象构造绑定对象构造时完成资源申请内存、锁、文件句柄、套接字等资源释放与对象析构绑定对象析构时自动释放资源依赖栈对象特性栈对象出作用域时会自动调用析构函数即使中途抛出异常栈展开机制也会保证析构函数被执行2.2 为什么需要 RAII手动管理资源存在两个核心问题容易遗漏释放操作造成资源泄漏异常场景下代码提前跳出释放逻辑永远不会执行cpp运行// 反例手动管理内存异常时泄漏 void badCase() { int* p new int(10); // 此处如果抛出异常delete p 永远不会执行 delete p; } // 正例RAII管理自动释放异常安全 void goodCase() { std::unique_ptrint p(new int(10)); // 即使抛异常栈展开时unique_ptr析构函数自动释放内存 }2.3 常见 RAII 应用场景内存管理unique_ptr、shared_ptr互斥锁std::lock_guard、std::unique_lock构造加锁析构解锁文件句柄封装FILE*构造打开文件析构关闭文件网络套接字封装 socket fd构造创建连接析构关闭连接三、C11 智能指针底层逻辑智能指针是 RAII 思想最典型的落地实现通过类封装原始指针在析构函数中自动释放内存完全替代手动new/delete。C11 标准库提供了三种核心智能指针。3.1 unique_ptr独占式智能指针核心特性独占所有权同一时刻只能有一个 unique_ptr 指向该对象零性能开销默认删除器下体积和原始指针完全一致无额外运行成本禁止拷贝支持移动通过 C11 移动语义转移所有权底层实现原理本质是一个封装了原始指针和删除器的模板类删除器作为模板参数存在。核心实现逻辑如下简化版cpp运行// 默认删除器 templatetypename T struct default_delete { void operator()(T* p) const { delete p; } }; templatetypename T, typename Deleter default_deleteT class unique_ptr { private: T* _ptr; Deleter _del; public: explicit unique_ptr(T* p nullptr) : _ptr(p) {} // 禁止拷贝语义 unique_ptr(const unique_ptr) delete; unique_ptr operator(const unique_ptr) delete; // 支持移动语义转移所有权 unique_ptr(unique_ptr other) noexcept : _ptr(other._ptr), _del(std::move(other._del)) { other._ptr nullptr; } unique_ptr operator(unique_ptr other) noexcept { if (this ! other) { reset(); // 释放当前资源 _ptr other._ptr; _del std::move(other._del); other._ptr nullptr; } return *this; } // 析构自动释放资源RAII核心体现 ~unique_ptr() { if (_ptr) { _del(_ptr); } } // 常用接口 void reset(T* p nullptr) { if (_ptr) _del(_ptr); _ptr p; } T* get() const { return _ptr; } T operator*() const { return *_ptr; } T* operator-() const { return _ptr; } };3.2 shared_ptr共享式智能指针核心特性共享所有权多个 shared_ptr 可以指向同一个对象通过引用计数管理生命周期引用计数为 0 时自动销毁对象并释放内存有额外性能开销需要维护引用计数体积是原始指针的 2 倍底层数据结构shared_ptr 内部包含两个指针对象指针指向实际的内存对象控制块指针指向一个独立的控制块Control Block存储强引用计数shared_count指向该对象的 shared_ptr 数量弱引用计数weak_count指向该对象的 weak_ptr 数量删除器、分配器等额外信息std::make_shared会一次性分配对象和控制块的内存减少内存分配次数性能更优也更安全。核心生命周期规则每拷贝一个 shared_ptr强引用计数 1每析构一个 shared_ptr强引用计数 - 1强引用计数减为 0 时销毁内存对象弱引用计数也减为 0 时销毁控制块简化版底层实现cpp运行templatetypename T class shared_ptr { private: T* _obj; // 对象指针 struct ControlBlock { int strong_ref; // 强引用计数 int weak_ref; // 弱引用计数 ControlBlock() : strong_ref(1), weak_ref(0) {} }* _ctrl; // 控制块指针 // 释放当前引用 void _release() { if (!_ctrl) return; if (--_ctrl-strong_ref 0) { delete _obj; // 强引用为0销毁对象 if (_ctrl-weak_ref 0) { delete _ctrl; // 弱引用也为0销毁控制块 } } } public: explicit shared_ptr(T* p nullptr) : _obj(p) { _ctrl p ? new ControlBlock() : nullptr; } // 拷贝构造强引用1 shared_ptr(const shared_ptr other) : _obj(other._obj), _ctrl(other._ctrl) { if (_ctrl) _ctrl-strong_ref; } // 析构释放引用 ~shared_ptr() { _release(); } // 拷贝赋值 shared_ptr operator(const shared_ptr other) { if (this ! other) { _release(); _obj other._obj; _ctrl other._ctrl; if (_ctrl) _ctrl-strong_ref; } return *this; } int use_count() const { return _ctrl ? _ctrl-strong_ref : 0; } T* get() const { return _obj; } T operator*() const { return *_obj; } T* operator-() const { return _obj; } templatetypename U friend class weak_ptr; };经典问题循环引用两个对象互相持有对方的 shared_ptr会导致强引用计数永远无法降为 0造成内存泄漏。cpp运行struct Node { shared_ptrNode next; }; // 循环引用A-next指向BB-next指向A两者强引用计数都为1永远不会释放解决方案将其中一方改为weak_ptr。3.3 weak_ptr弱引用智能指针核心特性不拥有所有权不会增加强引用计数不影响对象生命周期仅作为 “观察者”用于监测对象是否存活专门用于解决 shared_ptr 的循环引用问题核心接口expired()判断对象是否已销毁lock()尝试提升为 shared_ptr对象存活则返回有效 shared_ptr否则返回空use_count()观测当前强引用计数简化版底层实现cpp运行templatetypename T class weak_ptr { private: T* _obj; typename shared_ptrT::ControlBlock* _ctrl; public: weak_ptr() : _obj(nullptr), _ctrl(nullptr) {} // 从shared_ptr构造仅增加弱引用计数 weak_ptr(const shared_ptrT sp) : _obj(sp._obj), _ctrl(sp._ctrl) { if (_ctrl) _ctrl-weak_ref; } ~weak_ptr() { if (!_ctrl) return; if (--_ctrl-weak_ref 0 _ctrl-strong_ref 0) { delete _ctrl; } } // 对象是否已销毁 bool expired() const { return !_ctrl || _ctrl-strong_ref 0; } // 提升为shared_ptr shared_ptrT lock() const { if (expired()) return shared_ptrT(nullptr); _ctrl-strong_ref; shared_ptrT sp; sp._obj _obj; sp._ctrl _ctrl; return sp; } };解决循环引用的原理将循环链路中其中一个指针改为 weak_ptr打破强引用循环cpp运行struct Node { weak_ptrNode next; // 改为弱引用不增加强引用计数 };此时 A 对象的强引用计数由外部决定B 的 next 不会增加 A 的计数当外部引用释放后计数归零正常销毁。3.4 线程安全说明引用计数的增减是原子操作多线程下不同 shared_ptr 对象指向同一个对象是安全的多线程同时读写同一个 shared_ptr 对象不是线程安全的需要加锁保护智能指针只保证指针本身的引用计数安全不保证指向对象的线程安全谢谢