IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 简介C++中的智能指针 -> 正文阅读

[C++知识库]简介C++中的智能指针

简介C++中的智能指针

本文主要介绍C++标准中的3种智能指针: unique_ptr, shared_ptr, weak_ptr
至于早期的 auto_ptr (在C++11标准中被列为 deprecated) 和 boost::scoped_ptr (不属于C++标准),不在本文范围内。
智能指针的原理及RAII,属于比较基础的内容,不再多说。

要使用这3种智能指针,必须包含头文件 <memory>.

1. unique_ptr

1.1 常用的构造方法与赋值操作符

顾名思义,

  • unique_ptr 的拷贝构造函数 和 参数为左值引用的 operator = 都是被禁止的。
  • 而参数为右值引用的 operator = 是可以的。 如下:
unique_ptr (const unique_ptr&) = delete;

unique_ptr& operator= (const unique_ptr&) = delete;

// 参数为右值引用的赋值操作符
unique_ptr& operator= (unique_ptr&& x) noexcept;
template <class U, class E>
unique_ptr& operator= (unique_ptr<U,E>&& x) noexcept;

由上可见,
一个左值的 unique_ptr 不能被赋值给另外一个 unique_ptr, 否则编译报错;
但右值的 unique_ptr 可以被赋值给另外的 unique_ptr, 因为作为右值,它马上就会消亡了;
所以使用 std::move 将左值转为右值再进行赋值也是可以的,但要注意,std::move 后的 unique_ptr 不能再被使用。

unique_ptr<string> p3 (new string("AAA")); 
unique_ptr<string> p4;
p4 = p3; // 编译报错!以左值引用为参数的赋值已被禁止。
p4 = unique_ptr<string>(new string("BBB"); // OK 
p4 = std::move(p3);   // OK,但 p3 相当于悬挂指针,不能再被使用。 

对于 unique_ptr 的初始化,也可以使用 make_unique ,会减少一次对指针的内存拷贝。

unique_ptr<int> p = make_unique<int>(10); 

make_unique 是到C++14才支持的。
如果要自己手写一个 make_unique, 可以如下:

template <typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args) {
    return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}

另外,unique_ptr可以通过构造函数指定对资源的deleter;
这一般并不常见,故本文不做展开,但也有一些示例如下。

以下是关于 unique_ptr 构造的示例程序:

#include <iostream>
#include <memory>

int main () {
  std::default_delete<int> d;
  std::unique_ptr<int> u1;
  std::unique_ptr<int> u2 (nullptr);
  std::unique_ptr<int> u3 (new int);
  std::unique_ptr<int> u4 (new int, d);     // 带 deleter 的构造
  std::unique_ptr<int> u5 (new int, std::default_delete<int>());  // 带 deleter 的构造
  std::unique_ptr<int> u6 (std::move(u5));
  std::unique_ptr<int> u7 (std::move(u6));

  std::cout << "u1: " << (u1?"not null":"null") << '\n';
  std::cout << "u2: " << (u2?"not null":"null") << '\n';
  std::cout << "u3: " << (u3?"not null":"null") << '\n';
  std::cout << "u4: " << (u4?"not null":"null") << '\n';
  std::cout << "u5: " << (u5?"not null":"null") << '\n';
  std::cout << "u6: " << (u6?"not null":"null") << '\n';
  std::cout << "u7: " << (u7?"not null":"null") << '\n';
  return 0;
}

1.2 重要成员函数:

  • operator * : 给指针解引用

  • operator -> : 通过指针调用成员函数/成员

  • operator bool : 查看所管理指针是否为 nullptr, 相当于 get() != nullptr

  • get(): 返回所管理指针

  • release(): 不再管理指针,注意,但也不会去释放所管理指针指向的空间;将所管理指针替换为 nullptr

  • reset(p=nullptr): 将原来管理的指针(如果有的话)的空间释放掉,然后管理新传入的指针p

  • swap(unique_ptr& x): 交换所管理的指针

pointer get()  const noexcept;
pointer release()  noexcept;
void reset (pointer p = pointer()) noexcept;
void swap (unique_ptr& x) noexcept;
explicit operator bool() const noexcept;

2. shared_ptr

2.1 常用的构造函数

常用的构造函数大致包括: 传入的参数为空、传入参数为new的指针、shared_ptr(左值或右值), weak_ptr(左值), unique_ptr(右值).
另有带 deleter 的构造函数、带 allocator 的构造函数、以及通过aliasing进行构造的构造函数,这些不在本文展开。
关于 aliasing 构造函数 和 owner_before 成员函数,可以参考笔者此前的一篇博文

// 默认构造函数
constexpr shared_ptr() noexcept;

// 从 nullptr 进行构造
constexpr shared_ptr(nullptr_t) : shared_ptr() {}

// 从传入的指针进行构造
template <class U> 
explicit shared_ptr (U* p);

// 拷贝构造函数 (通过常量左值引用进行构造)
shared_ptr (const shared_ptr& x) noexcept;
template <class U> 
shared_ptr (const shared_ptr<U>& x) noexcept;

// 从 weak_ptr 进行构造 
template <class U> 
explicit shared_ptr (const weak_ptr<U>& x);

// 移动构造
shared_ptr (shared_ptr&& x) noexcept;

template <class U> 
shared_ptr (shared_ptr<U>&& x) noexcept;

// 从 unique_ptr 构造,也是移动构造
template <class U, class D> 
shared_ptr (unique_ptr<U,D>&& x);

另外,可以使用 std::make_shared 进行构造,它用来消除显式的使用 new,所以 make_shared 会根据传入的参数来创建对象,然后返回指向该对象的 shared_ptr.
之所以推荐使用 make_shared, 是可以减少一次对指针的内存拷贝。

std::shared_ptr<int> p = std::make_shared<int> (20);

以下是示例程序:

#include <iostream>
#include <memory>

struct C {int* data;};

int main () {
  std::shared_ptr<int> p1;
  std::shared_ptr<int> p2 (nullptr);
  std::shared_ptr<int> p3 (new int);
  std::shared_ptr<int> p4 (new int, std::default_delete<int>());  // 带 deleter的构造函数
  std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());  // 带 deleter的构造函数
  std::shared_ptr<int> p6 (p5);
  std::shared_ptr<int> p7 (std::move(p6));
  std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));
  std::shared_ptr<C> obj (new C);
  std::shared_ptr<int> p9 (obj, obj->data); // aliasing 构造函数

  std::cout << "use_count:\n";
  std::cout << "p1: " << p1.use_count() << '\n';    // 0
  std::cout << "p2: " << p2.use_count() << '\n';    // 0
  std::cout << "p3: " << p3.use_count() << '\n';    // 1
  std::cout << "p4: " << p4.use_count() << '\n';    // 1
  std::cout << "p5: " << p5.use_count() << '\n';    // 2
  std::cout << "p6: " << p6.use_count() << '\n';    // 0
  std::cout << "p7: " << p7.use_count() << '\n';    // 2
  std::cout << "p8: " << p8.use_count() << '\n';    // 1
  std::cout << "obj: " << obj.use_count() << '\n';  // 2
  std::cout << "p9: " << p9.use_count() << '\n';    // 2
  return 0;
}

2.2 重要的成员函数

  • operator = : 其参数主要是常量左值引用、右值引用、unique_ptr的右值引用;

  • operator * : 指针解引用,等于调用 *get()

  • operator ->: 调用指针的成员函数/成员

  • operator bool: 检查是否为nullptr

  • get(): 返回所管理的指针。使用 sp 和 sp.get() 是一样的。

  • reset(): 不再管理原来的指针,计数减一

  • reset( p ): 不再管理原来的指针,而开始管理新传入的指针p,即原来的指针计数减一,新的指针的计数加一

  • swap(x): 交换所管理的指针

  • use_count(): 查看资源所有者个数。

  • unique(): 返回是否是独占所有权,是为1,否为0

element_type* get() const noexcept;

void reset() noexcept;
template <class U> void reset (U* p);

void swap (shared_ptr& x) noexcept;

long int use_count() const noexcept;

bool unique() const noexcept;

总结一下, shared_ptr 与 unique_ptr 相同或相近的成员函数如下:

  • get()
  • reset() / reset( p )
  • swap§
  • operator *
  • operator ->
  • operator bool

unique_ptr 有而 shared_ptr 没有的成员函数:

  • release()

shared_ptr 有而 unique_ptr 没有的成员函数:

  • use_count()
  • unique()

2.3 shared_ptr 的实现

shared_ptr的实现: 关键是它包含了一个计数器类的指针,所指的空间在堆上;多个shared_ptr都是共享这个指针所指向的counter

shared_ptr 的实现图如下:
http://images.cnitblog.com/blog/112777/201301/28051715-e28b1c2264504cb1a275b916a641ecbb.png

该图中也给出了计数器类的实现,即一个计数器既包括各个shared_ptr共享的use_count,也包括weak_ptr共享的weak_count.

2.4 shared_ptr 是不是线程安全的

陈硕的这篇文章有详细论述。

这里只给出结论:
shared_ptr 的引用计数本身是安全且无锁的,但对象的读写则不是;
因为 shared_ptr 有2个数据成员(即计数器指针和所管理的指针),所以读写操作不能原子化;
如果多个线程读写同一个 shared_ptr 对象,那么需要加锁。

3. weak_ptr

当2个对象各使用一个shared_ptr成员变量指向对方,就会造成循环引用,使得这2个shared_ptr的引用计数永远不可能降为0,从而导致内存泄漏。
weak_ptr就是用来解决shared_ptr相互引用时的死锁问题,它是一种弱引用,不会增加对象的引用计数。
以上问题的解决方案就是,将其中一个 shared_ptr 改为 weak_ptr.

3.1 关于构造与赋值

  • weak_ptr的构造、析构、拷贝、赋值,都不会引起引用记数的增加或减少;
  • weak_ptr 只可以从一个 shared_ptr 或另一个 weak_ptr 对象进行构造或赋值;
  • weak_ptr 只有拷贝构造,而没有也不需要有移动构造
// 构造
constexpr weak_ptr() noexcept;

// 拷贝构造,没有移动构造
weak_ptr (const weak_ptr& x) noexcept;
template <class U> weak_ptr (const weak_ptr<U>& x) noexcept;
template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;

// 赋值操作符
weak_ptr& operator= (const weak_ptr& x) noexcept;
template <class U> weak_ptr& operator= (const weak_ptr<U>& x) noexcept;
template <class U> weak_ptr& operator= (const shared_ptr<U>& x) noexcept;

3.2 常用的成员函数

  • weak_ptr和shared_ptr之间可以相互转化:
    shared_ptr 可以直接赋值给 weak_ptr;
    weak_ptr 通过调用lock()函数来获得 shared_ptr

  • 不能通过weak_ptr直接访问对象的成员函数/成员,而必须通过lock()函数先转成shared_ptr,然后再访问对象的成员函数/成员。

  • weak_ptr 没有重载 operator * 和 operator ->

  • weak_ptr 在使用前需要检查合法性,即调用 expired(),它用于检测所管理的对象是否已经释放

  • use_count(): 返回对象的引用计数.

  • reset(): 将 weak_ptr 置空.

  • swap(): 交换 shared_ptr

shared_ptr<element_type> lock() const noexcept;

bool expired() const noexcept;

long int use_count() const noexcept;

void reset() noexcept;

void swap (weak_ptr& x) noexcept;

(完)

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-07 13:40:26  更:2021-10-07 13:42:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 1:35:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码