C++基础知识(3)智能指针

1. 智能指针分类

  • 共享型智能指针(shared_ptr):同一块堆内存可以被多个shared_ptr拥有。
  • 独享型智能指针(unique_ptr):同一块堆内存只能被一个unique_ptr拥有。
  • 弱引用型智能指针(weak_ptr):也是一种共享型智能指针,算是对共享型智能指针的补充。

2. shared_ptr

2.1 工作原理

(1)我们在分配内存时,堆上的内存必须通过栈上的内存来寻址,即通过栈上的指针来寻址堆内存,也是唯一的方式。
(2)在堆内存中添加一个引用计数,记录有几个指针指向它,当引用计数为0时,操作系统会自动释放该内存。

2.2 常用操作

(1)初始化

#include<iostream>
#include<memory>
void testPtrMian()
{
    /*
    * 方法一
    * 使用new进行初始化,不会内存泄漏但不建议
    **/
    std::shared_ptr<int> sharedI(new int(100));
    /*
    * 方法二
    * 使用make_shared函数进行初始化
    **/
    std::shared_ptr<int> sharedI = std::make_shared<int>(100);
    /*
    * 方法三
    * 使用普通指针进行初始化
    **/
    nt* pi = new int(100);
    std::shared_ptr<int> sharedI(pi);
    delete(pi); // 错误,内存被重复释放
    /*
    * 方法四
    * 使用复制构造函数进行初始化
    **/
    std::shared_ptr<int> sharedI = std::make_shared<int>(100);
    std::shared_ptr<int> sharedI2(sharedI);
}

(2)引用计数
智能指针是通过引用计数来判断内存的释放时机,使用use_count()函数可以得到shared_ptr的引用计数。
(3)share_ptr的操作和普通指针完全一致,除了数组支持不好。
(4)常用函数
unique():判断该shared_ptr对象是否独占,独占返回true,否则返回false。
reset():无参数,即释放控制权。有参数,即释放控制权并指向新的堆内存。
get():千万不要使用!如果要用也不要delete返回的指针。返回的是能操作的指针。
swap():交换两个智能指针指向的堆内存。

#include<iostream>
#include<memory>
int testPtrMian()
{
    std::shared_ptr<int> sharedI(new int(100)); 
    std::cout << sharedI.use_count() << std::endl; // 输出:1
    std::cout << sharedI.unique() << std::endl; // 输出:true 即 1
    std::shared_ptr<int> sharedI2(sharedI);
    std::cout << sharedI.use_count() << std::endl; // 输出:2
    std::cout << sharedI.unique() << std::endl; // 输出:false 即 0
    sharedI2.reset(); // 释放控制权,引用计数-1
    sharedI2.reset(new int(1000)); // 指向新的堆内存
    sharedI.swap(sharedI2); // *sharedI = 1000,*sharedI2 = 100,也可以使用std::swap(sharedI, sharedI2)
    std::cout << sharedI.use_count() << std::endl; // 输出:1
    std::cout << sharedI.unique() << std::endl; // 输出:true 即 1
    return 0;
}

(5)智能指针创建数组

int testPtrMian()
{
    std::shared_ptr<int> sharedI(new int[100]()); 
    std::cout << sharedI[20] << std::endl;  // 错误,不能直接做数组操作
    std::cout << sharedI.get()[20] << std::endl; // 正确
    return 0;
}

(6)智能指针传参
作为函数传参时,与普通参数一样,按值传递的方式就可以。这是因为shared_ptr的大小固定为8或16个字节(两倍指针大小,32位系统指针为4个字节,64位系统指针为8个字节,即shared_ptr中有两个指针),所以直接值传递就可以了

void test(std::shared_ptr<int> testSharedPtr) {}
int testPtrMian()
{
    std::shared_ptr<int> sharedI = std::make_shared<int>(100); 
    test(sharedI);
    return 0;
}

3. weak_ptr

weak_ptr主要用来弥补shared_ptr的循环引用问题。

3.1 循环引用问题

#include<iostream>
#include<memory>
class A
{
public:
    std::shared_ptr<B> sharedB;
    ~A() {} // 只有析构函数执行后,才能释放成员变量
};
class B
{
public:
    std::shared_ptr<A> sharedA;
    ~B() {} // 只有析构函数执行后,才能释放成员变量
};
void testPtrMian()
{
    std::shared_ptr<A> sharedPtrA = std::make_shared<A>(); // 指向A的计数为1
    std::shared_ptr<B> sharedPtrB = std::make_shared<B>(); // 指向B的计数为1
    sharedPtrA->sharedB = sharedPtrB; // 指向B的计数为2
    sharedPtrB->sharedA = sharedPtrA; // 指向A的计数为2
}

上述代码,testPtrMian()结束后,释放sharedPtrA,sharedPtrB,此时指向A的计数为1,指向B的计数为1,因为A、B存在指向,所以不会调用析构函数,更不会释放成员变量。死锁发生,这就是循环引用问题。

3.2 weak_ptr的使用

将其中一个替换成weak_ptr则不会有内存不释放的问题,因为weak_ptr不影响引用计数,同时weak_ptr需要绑定到shared_ptr上次才能使用。

#include<iostream>
#include<memory>
class A
{
public:
    std::weak_ptr<B> weakB;
    ~A() {}
};
class B
{
public:
    std::shared_ptr<A> sharedA;
    ~B() {}
};
void testPtrMian()
{
    std::shared_ptr<A> sharedPtrA = std::make_shared<A>(); // 指向A的计数为1
    std::shared_ptr<B> sharedPtrB = std::make_shared<B>(); // 指向B的计数为1
    sharedPtrA->weakB = sharedPtrB; // 指向B的计数为1,weak_ptr不影响引用计数
    sharedPtrB->sharedA = sharedPtrA; // 指向A的计数为2
}

4. unique_ptr

我们在使用智能指针时,一般优先考虑unique_ptr,因为消耗更小,如果内存需要共享,才使用shared_ptr。
(1)初始化方法与shared_ptr基本一致。
(2)注意:unique_ptr禁止复制构造初始化,也禁止使用赋值运算符。
(3)unique_ptr允许移动构造,移动赋值。移动语义代表之前的对象已经失去意义。
(4)reset()函数与shared_ptr完全一样。
(5)unique_ptr与普通指针大小相同,因此比shared_ptr小一倍。

void testPtrMian()
{
    std::unique_ptr<int> uniqueI = std::make_unique<int>(100);
    std::unique_ptr<int> uniqueI2 = uniqueI; // 错误,不能使用赋值运算符
    std::unique_ptr<int> uniqueI2(uniqueI); // 错误,不能使用复制构造
    std::unique_ptr<int> uniqueI2(std::move(uniqueI));  // 正确,可以使用移动构造
}

(6)将unique_ptr对象转化为shared_ptr对象
注意:shared_ptr对象无法转化为unique_ptr对象。

void test(std::unique_ptr<int> uniqueI)
{
    // 转换时,先将unique_ptr转化为右值,同时保证之后不再使用unique_ptr对象
    std::shared_ptr<int> sharedI(std::move(uniqueI)); 
}

5. 智能指针的使用范围

5.1 不能使用智能指针的情况

必须使用C语言的指针:
(1)网络传输函数,如#include<WinSock2.h>头文件下的send()recv()函数,只能使用C语言的指针。

在这里插入图片描述
(2)C语言的文件操作部分,不过C+已经有了替代品。

5.2 使用什么智能指针

(1)优先使用unique_ptr,需要共享时再使用shared_ptr。
(2)当使用shared_ptr时,遇到了循环引用问题,考虑联合weak_ptr一起使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值