C++11 智能指针学习笔记

非常棒的学习博客

在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。

1. shared_ptr

共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针 shared_ptr 是一个模板类。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。

C++11中提供了三种智能指针,使用这些智能指针时需要引用头文件 <memory>

  • std::shared_ptr:共享的智能指针
  • std::unique_ptr:独占的智能指针
  • std::weak_ptr:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视 shared_ptr 的。

共享智能指针对象初始化完毕之后就指向了要管理的那块堆内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数 use_count()

初始化

构造函数初始化
// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);

shared_ptr<int> ptr1(new int(123)); // 管理一个 int 型数据对应的堆内存
shared_ptr<char> ptr2(new char[12]);  // 管理字符数组对应的堆内存

int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);// error, 编译不会报错, 运行会出错,不要使用一个原始指针初始化多个shared_ptr。
拷贝和移动构造函数初始化
shared_ptr<int> ptr1(new int(520));  // 构造函数初始化
shared_ptr<int> ptr2(ptr1);	  // 拷贝构造函数
shared_ptr<int> ptr3 = ptr1;  // 拷贝赋值函数
shared_ptr<int> ptr4(std::move(ptr1));  // 移动构造函数,转移所有权,此时 ptr1指针置空
shared_ptr<int> ptr5 = std::move(ptr2); // 移动赋值函数,转移所有权,此时 ptr2指针置空
  1. 使用move不会使内存引用计数增加,作为move参数的 p 指针会被置空
  2. 使用拷贝构造函数新增的指针会使内存引用计数增加
  3. 作为参数传递给一个函数以及作为函数的返回值时,内存计数器也会增加
std::make_shared 初始化(最安全的分配和使用动态内存的方法)

make_shared 也定义在头文件 memory

// 例子
shared_ptr<int> ptr1 = make_shared<int>(520);
reset 方法初始化
shared_ptr<int> ptr;
ptr.reset(new int(123)); // ptr管理一个整数数据 
ptr.reset();  // 内存释放,ptr指针置空

获取原始指针

调用共享智能指针类提供的 get() 方法可以获取原始内存的地址

shared_ptr<int> ptr1 = make_shared<int>(33);
int *ptr2 = ptr1.get();

指定删除器

当智能指针管理的内存对应的引用计数变为0的时候,这块内存就会被智能指针析构掉了。另外,我们在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数,我们只需要进行实现,其调用是由智能指针完成的。

// 自定义删除器函数,释放int型内存
void deleteIntPtr(int* p){
    delete p;
    cout << "int 型内存被释放了...";
}
void test() {
	shared_ptr<int> ptr(new int(250), deleteIntPtr);
	shared_ptr<int> ptr(new int(250), [](int* p) {delete p; }); // 使用lambda表达式也可以
}

函数的参数就是智能指针管理的内存的地址,有了这个地址之后函数体内部就可以完成删除操作了。

删除动态数组

shared_ptr<int> ptr(new int[10], default_delete<int[]>()); 		// c++提供的删除函数
shared_ptr<int> ptr(new int[10], [](int* p) {delete[] p; }); 	// lambda表达式

可以自己封装一个 make_shared_array 方法来让 shared_ptr 支持数组

template <typename T>
shared_ptr<T> make_shared_array(size_t size)
{
    // 返回匿名对象
    return shared_ptr<T>(new T[size], default_delete<T[]>());
}
void test() {
    shared_ptr<int> ptr1 = make_shared_array<int>(10);
    cout << ptr1.use_count() << endl;
}

什么是 size_t
size_t 是一种无符号的整型数,它的取值没有负数,在数组中也用不到负数,而它的取值范围是整型数的双倍。sizeof操作符的结果类型是 size_t ,它在头文件中 typedef 为 unsigned int 类型。

typedef unsigned int size_t

2. unique_ptr

初始化

std::unique_ptr 是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针对象,但是不允许通过赋值将一个 unique_ptr 赋值给另一个unique_ptr

// 通过构造函数初始化对象
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2 = ptr1; // error, 不允许将一个unique_ptr赋值给另一个unique_ptr

//  转移所有权,函数返回值赋值
unique_ptr<int> func()
{
    return unique_ptr<int>(new int(520));
}
void test() {
    unique_ptr<int> ptr2 = move(ptr1);  // 转移所有权,此时 ptr1指针置空
    unique_ptr<int> ptr3 = func();  // 通过函数返回给其他的 unique_ptr
}

// reset
// 使用reset方法可以让unique_ptr解除对原始内存的管理,也可以用来初始化一个独占的智能指针。
unique_ptr<int> ptr1;
ptr1.reset(new int(10));
ptr1.reset(); // ptr1指针置空

get()方法

shared_ptr 相同

删除器

unique_ptr 指定删除器和 shared_ptr 指定删除器是有区别的,unique_ptr 指定删除器的时候需要确定删除器的类型,所以不能像 shared_ptr 那样直接指定删除器。

实例:

shared_ptr<int> ptr1(new int(10), [](int*p) {delete p; });	// ok
unique_ptr<int> ptr1(new int(10), [](int*p) {delete p; });	// error

// 使用函数指针
using func_ptr = void(*)(int*);
unique_ptr<int, func_ptr> ptr1(new int(10), [](int*p) {delete p; });

在上面的代码中第7行,func_ptr 的类型和 lambda表达式 的类型是一致的。
在lambda表达式没有捕获任何变量的情况下是正确的,但是如果捕获了变量,编译时则会报错:

using func_ptr = void(*)(int*);
unique_ptr<int, func_ptr> ptr1(new int(10), [&](int*p) {delete p; });	// error

上面的代码中错误原因是这样的,在lambda表达式没有捕获任何外部变量时,可以直接转换为函数指针,一旦捕获了就无法转换了,如果想要让编译器成功通过编译,那么需要使用可调用对象包装器来处理声明的函数指针:

#include <functional> // function 的头文件
unique_ptr<int, function<void(int*)>> ptr1(new int(10), [&](int*p) {delete p; });

weak_ptr

弱引用智能指针 std::weak_ptr 可以看做是 shared_ptr 的助手,它不管理 shared_ptr 内部的指针。std::weak_ptr 没有重载操作符 *->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视 shared_ptr 中管理的资源是否存在。

shared_ptr<int> sp(new int);

weak_ptr<int> wp1;  	 // 构造了一个空weak_ptr对象
weak_ptr<int> wp2(wp1);	 //  拷贝构造
weak_ptr<int> wp3(sp); 	 // 通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象
weak_ptr<int> wp4;		
wp4 = sp;				// wp4监视sp(隐式类型转换)
weak_ptr<int> wp5;
wp5 = wp3;				// 赋值

use_count 方法

通过调用 std::weak_ptr类提供的use_count()方法可以获得当前所观测资源的引用计数

上述代码中,wp3wp4wp5监测的资源是同一个

expired 方法

通过调用std::weak_ptr类提供的expired()方法来判断所观测的资源(shared_ptr指向的内存)是否已经被释放。若 w.use_count() 为 0,返回 true,否则返回 false
shared_ptr 指针可以通过 reset() 释放资源

lock 方法

通过调用std::weak_ptr 类提供的 lock() 方法来获取所监测的shared_ptr对象

reset 方法

通过调用std::weak_ptr类提供的reset()方法来放弃监视对象,使其不监测任何资源。
因为不再监控任何资源,因此

返回管理this的shared_ptr

C++11中为我们提供了一个模板类叫做std::enable_shared_from_this<T>,这个类中有一个方法叫做shared_from_this(),通过这个方法可以返回一个共享智能指针。在函数的内部就是使用weak_ptr来监测this对象,并通过调用weak_ptrlock()方法返回一个shared_ptr对象。

class Test : public enable_shared_from_this<Test>
{
public:
    shared_ptr<Test> getSharedPtr()
    {
        return shared_from_this();
    }
 //   shared_ptr<Test> getSharedPtr() {return shared_ptr<Test>(this);}
 // 直接返回this指针会导致通过getSharedPtr()初始化的shared_ptr的引用计数不会增加,最后被对象被析构两次的错误
    ~Test()
    {
        cout << "class Test is disstruct ..." << endl;
    }
};
int main() 
{
    shared_ptr<Test> sp1(new Test); // 必须先对 sp1初始化,保证weak_ptr具有检测对象,才能调用下边的getSharedPtr()
    cout << "use_count: " << sp1.use_count() << endl;
    shared_ptr<Test> sp2 = sp1->getSharedPtr();
    cout << "use_count: " << sp1.use_count() << endl;
    return 0;
}

解决循环引用问题

在这里插入图片描述

ap 和 aptr 是指向类TA的共享指针,bp 和 bptr 是指向类TB的贡献指针

红色箭头表示共享指针指向申请的空间,蓝色箭头是对象里的智能指针指向内存空间

共享智能指针ap、bp对TA、TB实例对象的引用计数变为2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类TA、TB的实例对象不能被析构,最终造成内存泄露。

通过使用weak_ptr可以解决这个问题,只要将类TA或者TB的任意一个成员改为weak_ptr,修改之后的代码如下:

struct TA;
struct TB; // 声明类

struct TA{
    weak_ptr<TB> bptr; // 将其中一个类里的shared_ptr改成weak_ptr即可解决
    ~TA()
    {
        cout << "class TA is disstruct ..." << endl;
    }
};
struct TB{
    shared_ptr<TA> aptr;
    ~TB()
    {
        cout << "class TB is disstruct ..." << endl;
    }
};
void testPtr(){
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->bptr = bp;
    bp->aptr = ap;
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;
}

上面程序中,在对类TA成员赋值时ap->bptr = bp;由于bptrweak_ptr类型,这个赋值操作并不会增加引用计数,所以bp的引用计数仍然为1,在离开作用域之后bp的引用计数减为0,类TB的实例对象被析构。

在类TB的实例对象被析构的时候,内部的aptr也被析构,其对TA对象的管理解除,内存的引用计数减为1,当共享智能指针ap离开作用域之后,对TA对象的管理也解除了,内存的引用计数减为0,类TA的实例对象被析构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值