跟我学c++中级篇——STL智能指针再述

2023-05-16

一、智能指针(smart pointer)

在前面的文章分析中对智能指针分析的还是比较多的,这里把一具体的遗漏以及一些新的感悟再总结一下,以之为鉴。什么是智能指针?在C/C++中,普通指针的管理,一直是贯穿整个编程过程的难点和痛点。谈到指针,多少人都是摇头。那么有没有解决的方式和改进的方法呢。俗话说得好,有问题就会有解决问题的人。智能指针虽然说不能够达到一劳永逸的解决原生指针的安全管理问题,但毕竟朝着改进整个原生指针的安全性上迈进了一大步。这也是智能指针在整个STL体系里不断的演进的原因。
大路不平旁人铲啊。

二、STL中的智能指针

在STL中的智能指针主要分成两大类即shared_ptr共享式和unique_ptr专有式或者说单一拥有。在更早期的STL中,提供过auto_ptr这种智能指针,但现在基本已经废弃掉了。
1、共享式(shared_ptr)
这种又有shared_ptr 和weak_ptr两种常见的智能指针。两种指针的出现其实是为了解决一个问题,也就是如果只有shared_ptr的情况下,如果多个智能指针对象互相持有的问题。这在种情况下,互相持有的智能指针因为彼此拥有对方的指针而使得相关的引用计数无法清空,从而在某种程度上造成内存泄露。而增加weak_ptr后,相互持有的智能指针中,一方可以是weak_ptr,它不会引起计数器的变化。但在使用前需要对其进行升级到shared_ptr的操作,如果为空,则表明相关资源已经释放。从宏观应用上来讲,没有达到根本的设计思想,但毕竟是将整个开发的安全度增加了许多,还是得大于失的。
另外一种情况下,是只是想把智能指针暴露给别人使用,但却不想让对方完成拥有,也可以采用使用weak_ptr的方式。这样,只要相关的资源没有被释放,就允许外部接口的使用。但却无法影响内部的安全性。

2、专有式(unique_ptr)
不过,在实际的开发中,大家可能也遇到过这种问题,就是说这个数据资源只想独占,而不允许别人来应用。在早期,一般可能通过类似锁机制的方式来处理类似的操作。但在新c++11后,提供了unique_ptr,通过move来达到资源的转移而不是资源的共享。换句话说,资源只能从一个控制者的手中转到另外一个控制者手中,否则一旦不再使用就会被释放。
一定需要明白的是,资源只能转移,不允许复制或者多人控制。

智能指针在多线程下一般认为是不安全的,如果想确保在多线程下操作的安全,请使用锁等机制来保障相关的data race。
需要说明的是,在c++11中,对数组应用智能指针还是稍微有一些不足,这需要自己来实现deleter,但在更高版本的c++17以后,智能指针对数组的支持也已经完成。这些不断迭代和更新的c++的最新的库的,就是支持着c++不断的向着未来前进的火种。

三、源码分析

看一下STL中善于智能指针的实现源码:
先看一下定义:

template< class T > class shared_ptr;

template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;

template <
    class T,
    class Deleter
> class unique_ptr<T[], Deleter>;

再看一下源码定义:

	// CLASS TEMPLATE unique_ptr SCALAR
template<class _Ty,
	class _Dx>	// = default_delete<_Ty>
	class unique_ptr
		: public _Unique_ptr_base<_Ty, _Dx>
	{	// non-copyable pointer to an object
public:
	typedef _Unique_ptr_base<_Ty, _Dx> _Mybase;
	typedef typename _Mybase::pointer pointer;
	typedef _Ty element_type;
	typedef _Dx deleter_type;

	using _Mybase::get_deleter;

	template<class _Dx2 = _Dx,
		_Unique_ptr_enable_default_t<_Dx2> = 0>
		constexpr unique_ptr() noexcept
			: _Mybase(pointer())
		{	// default construct
		}

	template<class _Dx2 = _Dx,
		_Unique_ptr_enable_default_t<_Dx2> = 0>
		constexpr unique_ptr(nullptr_t) noexcept
			: _Mybase(pointer())
		{	// null pointer construct
		}
                //空指针的处理
	unique_ptr& operator=(nullptr_t) noexcept
		{	// assign a null pointer
		reset();
		return (*this);
		}

	template<class _Dx2 = _Dx,
		_Unique_ptr_enable_default_t<_Dx2> = 0>
		explicit unique_ptr(pointer _Ptr) noexcept
			: _Mybase(_Ptr)
		{	// construct with pointer
		}

	template<class _Dx2 = _Dx,
		enable_if_t<is_constructible_v<_Dx2, const _Dx2&>, int> = 0>
		unique_ptr(pointer _Ptr, const _Dx& _Dt) noexcept
			: _Mybase(_Ptr, _Dt)
		{	// construct with pointer and (maybe const) deleter&
		}

	template<class _Dx2 = _Dx,
		enable_if_t<conjunction_v<negation<is_reference<_Dx2>>,
			is_constructible<_Dx2, _Dx2>>, int> = 0>
		unique_ptr(pointer _Ptr, _Dx&& _Dt) noexcept
			: _Mybase(_Ptr, _STD move(_Dt))
		{	// construct by moving deleter
		}

	template<class _Dx2 = _Dx,
		enable_if_t<conjunction_v<is_reference<_Dx2>,
			is_constructible<_Dx2, remove_reference_t<_Dx2>>>, int> = 0>
		unique_ptr(pointer, remove_reference_t<_Dx>&&) = delete;

	unique_ptr(unique_ptr&& _Right) noexcept
		: _Mybase(_Right.release(),
			_STD forward<_Dx>(_Right.get_deleter()))
		{	// construct by moving _Right
		}

	template<class _Ty2,
		class _Dx2,
		enable_if_t<conjunction_v<negation<is_array<_Ty2>>,
			is_convertible<typename unique_ptr<_Ty2, _Dx2>::pointer, pointer>,
			conditional_t<is_reference_v<_Dx>, is_same<_Dx2, _Dx>, is_convertible<_Dx2, _Dx>>
		>, int> = 0>
		unique_ptr(unique_ptr<_Ty2, _Dx2>&& _Right) noexcept
			: _Mybase(_Right.release(),
				_STD forward<_Dx2>(_Right.get_deleter()))
		{	// construct by moving _Right
		}

 #if _HAS_AUTO_PTR_ETC
	template<class _Ty2,
		enable_if_t<conjunction_v<is_convertible<_Ty2 *, _Ty *>,
			is_same<_Dx, default_delete<_Ty>>>, int> = 0>
		unique_ptr(auto_ptr<_Ty2>&& _Right) noexcept
			: _Mybase(_Right.release())
		{	// construct by moving _Right
		}
 #endif /* _HAS_AUTO_PTR_ETC */

	template<class _Ty2,
		class _Dx2,
		enable_if_t<conjunction_v<negation<is_array<_Ty2>>,
			is_assignable<_Dx&, _Dx2>,
			is_convertible<typename unique_ptr<_Ty2, _Dx2>::pointer, pointer>
		>, int> = 0>
		unique_ptr& operator=(unique_ptr<_Ty2, _Dx2>&& _Right) noexcept
		{	// assign by moving _Right
		reset(_Right.release());
		this->get_deleter() = _STD forward<_Dx2>(_Right.get_deleter());
		return (*this);
		}

	unique_ptr& operator=(unique_ptr&& _Right) noexcept
		{	// assign by moving _Right
		if (this != _STD addressof(_Right))
			{	// different, do the move
			reset(_Right.release());
			this->get_deleter() = _STD forward<_Dx>(_Right.get_deleter());
			}
		return (*this);
		}

	void swap(unique_ptr& _Right) noexcept
		{	// swap elements
		_Swap_adl(this->_Myptr(), _Right._Myptr());
		_Swap_adl(this->get_deleter(), _Right.get_deleter());
		}

	~unique_ptr() noexcept
		{	// destroy the object
		if (get() != pointer())
			{
			this->get_deleter()(get());
			}
		}

	_NODISCARD add_lvalue_reference_t<_Ty> operator*() const
		{	// return reference to object
		return (*get());
		}

	_NODISCARD pointer operator->() const noexcept
		{	// return pointer to class object
		return (this->_Myptr());
		}

	_NODISCARD pointer get() const noexcept
		{	// return pointer to object
		return (this->_Myptr());
		}

	explicit operator bool() const noexcept
		{	// test for non-null pointer
		return (get() != pointer());
		}

	pointer release() noexcept
		{	// yield ownership of pointer
		pointer _Ans = get();
		this->_Myptr() = pointer();
		return (_Ans);
		}

	void reset(pointer _Ptr = pointer()) noexcept
		{	// establish new pointer
		pointer _Old = get();
		this->_Myptr() = _Ptr;
		if (_Old != pointer())
			{
			this->get_deleter()(_Old);
			}
		}
                //可以看到这两个赋值的符号被禁止了
	unique_ptr(const unique_ptr&) = delete;
	unique_ptr& operator=(const unique_ptr&) = delete;
	};

在前面也提到过,智能指针其实就是使用引用计数器,源码中就可以看到相关的计数器的身影。

四、实例

看一下一个相关的实例(cppreference.com):

 #include <iostream>
#include <vector>
#include <memory>
#include <cstdio>
#include <fstream>
#include <cassert>
#include <functional>
 
// helper class for runtime polymorphism demo below
struct B {
  virtual void bar() { std::cout << "B::bar\n"; }
  virtual ~B() = default;
};
struct D : B
{
    D() { std::cout << "D::D\n";  }
    ~D() { std::cout << "D::~D\n";  }
    void bar() override { std::cout << "D::bar\n";  }
};
 
// a function consuming a unique_ptr can take it by value or by rvalue reference
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
    p->bar();
    return p;
}
 
// helper function for the custom deleter demo below
void close_file(std::FILE* fp) { std::fclose(fp); }
 
// unique_ptr-based linked list demo
struct List {
  struct Node {
    int data;
    std::unique_ptr<Node> next;
    Node(int data) : data{data}, next{nullptr} {}
  };
  List() : head{nullptr} {};
  // N.B. iterative destructor to avoid stack overflow on long lists
  ~List() { while(head) head = std::move(head->next); }
  // copy/move and other APIs skipped for simplicity
  void push(int data) {
    auto temp = std::make_unique<Node>(data);
    if(head) temp->next = std::move(head);
    head = std::move(temp);
  }
private:
  std::unique_ptr<Node> head;
};
 
int main()
{
  std::cout << "1) Unique ownership semantics demo\n";
  {
      auto p = std::make_unique<D>(); // p is a unique_ptr that owns a D
      auto q = pass_through(std::move(p)); 
      assert(!p); // now p owns nothing and holds a null pointer
      q->bar();   // and q owns the D object
  } // ~D called here
 
  std::cout << "2) Runtime polymorphism demo\n";
  {
    std::unique_ptr<B> p = std::make_unique<D>(); // p is a unique_ptr that owns a D
                                                  // as a pointer to base
    p->bar(); // virtual dispatch
 
    std::vector<std::unique_ptr<B>> v;  // unique_ptr can be stored in a container
    v.push_back(std::make_unique<D>());
    v.push_back(std::move(p));
    v.emplace_back(new D);
    for(auto& p: v) p->bar(); // virtual dispatch
  } // ~D called 3 times
 
  std::cout << "3) Custom deleter demo\n";
  std::ofstream("demo.txt") << 'x'; // prepare the file to read
  {
      std::unique_ptr<std::FILE, decltype(&close_file)> fp(std::fopen("demo.txt", "r"),
                                                           &close_file);
      if(fp) // fopen could have failed; in which case fp holds a null pointer
        std::cout << (char)std::fgetc(fp.get()) << '\n';
  } // fclose() called here, but only if FILE* is not a null pointer
    // (that is, if fopen succeeded)
 
  std::cout << "4) Custom lambda-expression deleter demo\n";
  {
    std::unique_ptr<D, std::function<void(D*)>> p(new D, [](D* ptr)
        {
            std::cout << "destroying from a custom deleter...\n";
            delete ptr;
        });  // p owns D
    p->bar();
  } // the lambda above is called and D is destroyed
 
  std::cout << "5) Array form of unique_ptr demo\n";
  {
      std::unique_ptr<D[]> p(new D[3]);
  } // calls ~D 3 times
 
  std::cout << "6) Linked list demo\n";
  {
    List l;
    for(long n = 0; n != 1'000'000; ++n) l.push(n);
    std::cout << "destroying 1'000'000 nodes... ";
  } // destroys all 1 million nodes
  std::cout << "Done.\n";
}
 

运行结果:

 1) Unique ownership semantics demo
D::D
D::bar
D::bar
D::~D
2) Runtime polymorphism demo
D::D
D::bar
D::D
D::D
D::bar
D::bar
D::bar
D::~D
D::~D
D::~D
3) Custom deleter demo
x
4) Custom lambda-expression deleter demo
D::D
D::bar
destroying from a custom deleter...
D::~D
5) Array form of unique_ptr demo
D::D
D::D
D::D
D::~D
D::~D
D::~D
6) Linked list demo
destroying 1'000'000 nodes... Done.

上面的例子来自于https://en.cppreference.com/w/cpp/memory/unique_ptr,cpp的权威网站。

五、总结

在学过Java或者其它语言的GC机制后,应该就会明白内存自动回收的基本基理和相关的实现手段和发展历程。其实,计数器这种方式相对来说是一种比较低级的内存管理方式,虽然对c++来说可能是一种重大的跨越,但对整个内存管理的机制来说,它仍然是一个比较初步的方式,它在多线程应用中,还是有着这样那样的细节的小问题需要处理。
更进一步来分析,这和其它语言中(如Rust)中的生命周期和使用权又有不小的可以理解的关系或者说类比的关系,从语言宏观的角度看,所有的语言都或多或少的有着相通的影子,从这一点来看语言的学习,才能更好的把握学习的重点和精髓,有的放矢。
但是,作为一种进步,无疑对开发者来说一种利好,希望这种利好不断的出现,c++的希望就会越来越大。
努力吧,归来的少年!
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

跟我学c++中级篇——STL智能指针再述 的相关文章

随机推荐

  • 漫谈程序员系列:咦,你也在混日子啊

    戳你一下 xff0c 疼吗 xff1f 混日子的定义 来自百度百科的定义 xff1a 生活等方面过得不怎么好 xff0c 无目标 xff0c 混混沌沌 混日子 xff1a 即没有理想 xff0c 没有抱负 xff0c 糊里糊涂地生活 也指工
  • QtAndroid详解(1):QAndroidJniObject

    Qt 5 3之后 xff0c 新增了 QtAndroid 名字空间 xff0c 内有下列四个方法 xff1a QAndroidJniObject AndroidActivity int androidSdkVersion void star
  • freeSWITCH安装、配置与局域网测试

    这次来说说 freeSWITCH 的安装和配置 1 安装 freeSWITCH 下载页面 xff1a https freeswitch org confluence display FREESWITCH Installation 我们 Wi
  • 就 3 点,提升工作效率

    要想提高工作效率 xff0c 不论你看什么书 xff0c 看什么文章 xff0c 用什么工具 xff0c 只有下面这三点最重要 xff1a 动力剖析自己 xff0c 找到改善的切入点付诸行动并且坚持 目标驱动 有目标才能高效 我们爬山 xf
  • Python3 下 ROS 的使用 cv_bridge

    Python 3 下 ROSmsg 转 cv2 项目中用到的 Tensorflow2 4 的环境 xff0c 该环境只支持python3 版本 xff0c 项目中遇到不少需要和 ROS 交互的地方 xff0c 所以不断探索 python3
  • 深度图和RGB图对齐

    深度图 canny RGB canny Alignment xff1a code span class token function import span cv2 span class token function import span
  • 认识romfs文件系统

    1 1 什么是romfs romfs 是一个只读文件系统 xff0c 主要用在 mainly for initial RAM disks of installation disks 使用romfs 文件系统可以构造出一个最小的内核 xff0
  • Livox Lidar 特征提取方式总结

    传统的 旋转式雷达 xff0c 激光固定在雷达的旋转部件上 xff0c 依靠转子的转动而获得360的旋转视野 xff0c 由于旋转部件的存在 xff0c 为了获得更加精准的数据 xff0c 需要跟多的人工校准和更复杂的设计 xff0c 也因
  • C++ 菜鸟之路 (四) boost::thread 多线程全解析

    boost thread 的一般用法 boost thread的几个函数 锁 lock 函数多线程函数的限制 官方解释 xff1a http www cplusplus com reference thread thread joinabl
  • ROS 之 advertise 详解

    在学习过程中接触到如下的一段话 span class hljs comment ROS handles span ros span class hljs tag NodeHandle span node tf span class hljs
  • Linux 下 openMP 效率并未提升的解决方案

    OpenMP 正确观察计算时间OpenMP 经验总结 xff08 1 xff09 openmp 线程使用范围 xff08 2 xff09 openmp 多层嵌套的问题 OpenMP 正确观察计算时间 在使用 openmp的过程中 xff0c
  • C++ Yaml文件解析安装及使用 yaml-cpp

    C 43 43 Yaml文件解析安装及使用 安装 yaml cpp克隆官方库编译 yaml cpp 示例代码robot cpprobot yaml编译 robot cpp运行结果 难点分析与总结什么是 a 与 so 文件静态链接库 a 与动
  • 点云数据格式解析 sensor_msgs::PointCloud2

    在使用多线激光的时候需要总是会碰到点云数据 xff0c 这里简单的接受一下点云数据 xff0c 并堆数据结构进行分析 xff0c 方便自己后期对点云特征数据进行处理 文章目录 Rviz中的点云数据点云数据结构分析点云数据 python 解析
  • Arduino 读取GPS 数据发送解析并发布ROS topic(二)

    Arduino 读取GPS 数据发送解析并发布ROS topic 一 https blog csdn net Fourier Legend article details 84107494 概述 本部分将主要讲将串口接受到的数据 xff0c
  • LOAM进行点云地图创建

    3D激光点云数据处理入门 xff08 一 xff09 使用LOAM进行点云地图创建 LOAM 原理简述topic关系算法分析算法伪代码 LOAM 建图实践创建你的 ROS Workspace下载LOAM Package下载数据包运行 LOA
  • ROS - teb_local_planner 参数总结

    参考官方教程 http wiki ros org teb local planner Tutorials 全英文看着有点累 在此总结一下调试的过程和小小的经验 安装 teb local planner sudo apt get instal
  • 路径规划算法(2) - A*寻路算法 python实现及解析

    代码 span class token comment coding 61 utf 8 span span class token keyword import span math span class token comment 启发距离
  • 使用FSMC驱动LCD以及数据线偏移的问题

    FSMC的理解 使用FSMC功能将8080接口的LCD当外部RAM来使用 xff08 数据传给LCD时没经过内部SRAM xff0c 所以一帧图片很大也可以直接传 xff09 xff0c 根据STM的地址分配图可以看出外部RAM的地址由0x
  • C++调用HTTP实现方式

    转自 xff1a http blog 163 com lyz sea blog static 11558670720118245052189 Http访问有两种方式 xff0c GET和POST xff0c 就编程来说GET方式相对简单点
  • 跟我学c++中级篇——STL智能指针再述

    一 智能指针 xff08 smart pointer xff09 在前面的文章分析中对智能指针分析的还是比较多的 xff0c 这里把一具体的遗漏以及一些新的感悟再总结一下 xff0c 以之为鉴 什么是智能指针 xff1f 在C C 43 4