ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

17. 智能指针上

17. 智能指针上 智能指针智能指针的必要性内存泄漏与异常安全智能指针smart pointer是行为类似于指针的类对象但这种对象还有额外功能——它拥有析构函数在对象过期时自动释放其指向的内存。本节介绍三个可帮助管理动态内存分配的智能指针模板auto_ptr、unique_ptr 和 shared_ptr。auto_ptr 是 C98 提供的方案C11 已将其摒弃deprecated并提供了另外两种方案。而函数终止时若指针是拥有析构函数的类对象其析构函数将在对象过期时释放指向的内存。规则限制auto_ptr 虽被摒弃但已使用多年若编译器不支持另外两种方案auto_ptr 将是唯一的选择。// 常规指针忘记 delete 或在异常路径下导致内存泄漏 void remodel(std::string str) { std::string* ps new std::string(str); // 分配堆内存 str *ps; // 使用数据 // 若中途抛出异常或忘记 delete内存泄漏 delete ps; // 手动释放不可靠 } ​ // 智能指针过期时析构函数自动释放内存 void remodel2(std::string str) { std::auto_ptrstd::string ps(new std::string(str)); // 托管堆对象 str *ps; // 正常使用 // 无需 deleteps 过期时析构函数自动释放 }三种智能指针模板与基本使用auto_ptr、unique_ptr 和 shared_ptr 都定义了类似指针的对象可以将 new 获得直接或间接的地址赋给这种对象当智能指针过期时其析构函数使用 delete 释放内存。要创建智能指针对象必须包含头文件 memory然后使用通常的模板语法实例化所需类型的指针如 auto_ptrdouble pd(new double);、unique_ptrdouble pdu(new double);、shared_ptrstring pss(new string);。智能指针模板位于命名空间 std 中。#include memory // 智能指针模板所在头文件 std::auto_ptrdouble pd(new double); // auto_ptr 指向 double std::unique_ptrdouble pdu(new double); // unique_ptr 指向 double std::shared_ptrstd::string pss(new std::string); // shared_ptr 指向 stringexplicit 构造函数与常规指针行为所有智能指针类都有一个以裸指针为参数的 explicit 构造函数将指针作为参数因此不允许需自动将指针裸指针转换为智能指针对象——pd p_reg;隐式转换常规指针double* p2 p_reg; 只是复制了一个地址4或8字节两者指向同一块内存。智能指针shared_ptrdouble p2 pd; 复制的是控制块此时两个智能指针共同管理同一块内存auto_ptrC98 的已废弃产物赋值会导致所有权转移原指针变空。所以不安全。std::shared_ptrdouble pd; double* p_reg new double; // pd p_reg; // 不允许隐式转换 pd std::shared_ptrdouble(p_reg); // 允许显式转换 ​ std::string vacation(a string literal); // 栈上的对象 // std::shared_ptrstd::string pvac(vacation); // 错误指向非堆内存 // pvac 过期时会对非堆内存执行 delete这是错误的赋值问题与三种避免策略若把智能指针赋给另一个同类型智能指针且两者指向同一对象程序将试图删除同一个对象两次。避免这种问题有三种方法定义赋值运算符执行深复制deep copy两个指针指向不同对象其中一个为另一个的副本建立所有权ownership概念auto_ptr 和 unique_ptr 采用的策略但 unique_ptr 更严格对特定对象只能有一个智能指针拥有它赋值操作转让所有权shared_ptr 采用的策略跟踪引用特定对象的智能指针数称为引用计数reference counting——赋值时计数加 1指针过期时计数减 1仅当最后一个指针过期时才调用 deletestd::auto_ptrstd::string ps(new std::string(temp)); std::auto_ptrstd::string vocation; vocation ps; // 所有权转让ps 失去对该对象的所有权 // shared_ptr 采用引用计数vocation ps 后计数从 1 增至 2 // 仅当最后一个指针过期时计数降至 0 才调用 deleteweak_ptr 解决循环引用weak_ptr 是配合 shared_ptr 使用的弱引用智能指针它指向对象但不增加引用计数也不拥有所有权。把循环中的一方改为 weak_ptr 即可打破循环。weak_ptr 不参与引用计数不会阻止对象被释放当最后一个 shared_ptr 销毁时对象照常析构。weak_ptr 不能直接解引用访问对象必须先 lock() 提升为 shared_ptr 再访问只能由 shared_ptr 构造或赋值不能单独管理 new 出来的对象。class BB; // 前向声明 class AA { public: std::weak_ptrBB m_bb_ptr; // 改为 weak_ptr不增加引用计数 }; class BB { public: std::shared_ptrAA m_aa_ptr; // 仍为 shared_ptr }; // ptr_a-m_bb_ptr ptr_b; // 计数不增加析构正常执行weak_ptr 的常用操作weak_ptr 提供三个关键操作expired() 判断所指向对象是否已被销毁lock() 尝试获取一个指向该对象的 shared_ptr对象已销毁时返回空 shared_ptruse_count() 返回共享该对象的 shared_ptr 数量不增加计数。weak_ptr 自身不拥有资源必须通过提升为 shared_ptr 才能安全访问lock() 把检查 获取合并为一步。规则限制lock() 返回的 shared_ptr 为空表示对象已销毁访问前应判空use_count() 只是查看不增加计数。std::shared_ptrint sp std::make_sharedint(42); std::weak_ptrint wp(sp); // 用 shared_ptr 初始化 weak_ptr // if (wp.expired()) ... // 判断对象是否已被销毁 // if (auto sp2 wp.lock()) ... // 提升为 shared_ptr 后安全访问 // wp.use_count(); // 查看引用计数不增加三种智能指针对比特性unique_ptrshared_ptrweak_ptr所有权独占共享弱引用引用计数无有无可拷贝否可移动是是可移动是是是能否直接访问对象直接直接需lock()转 shared_ptr开销低高原子操作 控制块低make_shared 的优势make_sharedT(args...) 是创建 shared_ptr 的推荐方式它把对象和控制块引用计数、删除器等一次性分配。一次内存分配减少了分配次数、缓存更友好且避免先 new 后构造 shared_ptr两步之间抛异常导致的内存泄漏更安全。规则限制make_shared 无法指定自定义删除器需要自定义删除器时才用 new 直接构造。#include memory std::shared_ptrint p1 std::make_sharedint(42); // 推荐一次分配 std::shared_ptrint p2(new int(42)); // 不推荐两步分配引用计数的增减规则shared_ptr 通过 use_count() 查看引用计数计数随操作按以下规则变化初始化 0 → 1拷贝构造 1赋值左操作数 -1 右操作数 1作为函数参数传递 1作为函数返回值 1析构或 reset() -1。引用计数是 shared_ptr 共享所有权的实现机制——只有最后一个指针过期、计数归零时才调用 delete。计数归零时自动释放对象不要依赖计数的精确值做逻辑判断。std::shared_ptrint p1 std::make_sharedint(42); std::shared_ptrint p2 p1; // 拷贝构造计数 1 → 2 // p1.use_count() 2 p2.reset(); // 释放计数 2 → 1 // p1.use_count() 1 // 最后一个指针销毁时计数归零才释放对象方法对原对象做了什么对指针本身内部指针做了什么返回值reset()立刻删除delete原对象置为nullptr空 可传参指向新对象void无release()不删除delete只是放弃管理权置为nullptr空返回裸指针原对象的地址
返回列表