首页 > 编程语言 >C++开发基础——智能指针

C++开发基础——智能指针

时间:2024-03-21 23:59:23浏览次数:30  
标签:std C++ 智能 shared unique ptr 指针

一,智能指针

1.智能指针简介

智能指针是用法和行为类似于指针的类对象。

智能指针的底层对原始指针做了一定的封装。

智能指针除了像指针一样可以存储变量的地址,还提供了其他功能,比如可以管理动态内存分配,对引用进行计数等。

当智能指针所指向的变量离开了作用域或被重置时,智能指针会自动释放该变量所占用的堆内存资源。

至于为什么要引入智能指针,可以参考下面这段代码:

void func_1()
{
       Sample* obj_one = new Sample();
       if(something_wrong())
              throw exception();
       delete obj_one;
}

每次调用该函数的时候,都需要在堆中申请一段内存,然后在函数的最后释放该内存。但是当函数运行期间出现异常的时候,delete将不被执行,此时申请到的内存得不到释放,会发生内存泄露。智能指针由于是类对象,该类对象可以在析构的时候自动释放智能指针所指向的内存。因此,如果此时使用智能指针代替原始指针,可以不用手动调用"delete/delete []",智能指针指向的堆内存会自动被释放。

上述代码可以改写为:

void func_2()
{
       auto obj_two = make_unique<Sample>();
       if(something_wrong())
              throw exception();
}

当unique_ptr实例离开作用域时(代码执行到了函数末尾,或者函数抛出异常),就会在其析构函数中自动释放obj_two对象所占有的内存资源。

标准库中提供了相应的类模板,它们可以将任何数据类型封装成智能指针,使用它们时,需要引入<memory>头文件。

智能指针常用的类模板有:

std::unique_ptr<T>

std::shared_ptr<T>

std::weak_ptr<T>

由上述的类模板可以生成三种类型的智能指针实例。这三种智能指针实例的区别在于,管理原始指针的方式不一样。

shared_ptr允许多个指针指向同一个变量。

unique_ptr则独占所指向的变量。

weak_ptr则指向shared_ptr所管理的变量。

2.智能指针的基础用法

1.智能指针的初始化

智能指针是基于类模板生成的,因此,要初始化一个智能指针,就必须声明指针所指向的数据类型,不然智能指针里面包含的原始指针是个空指针。

初始化方式一,在智能指针构造函数中new一个新对象。

struct C{
    int a;
    int b;
};
std::shared_ptr<C> p1(new C);
std::unique_ptr<int> p2(new int(40));

初始化方式二,采用make_shared函数(C++11标准)、make_unique函数(C++14标准)。

std::shared_ptr<int> p3 = std::make_shared<int>(15);
std::unique_ptr<int> p4 = std::make_unique<int>(10);

智能指针在初始化时,还可以用于指向动态分配的数组。

代码样例,创建长度为10的整型数组:

//方式一
auto Array_1 = make_unique<int[]>(10);
//方式二
std::unique_ptr<int[]> Array_2(new int[10]);
//类型+[],表示初始化指向数组的智能指针
//后面的具体用法和数组类似
Array_1[0] = 1;
Array_2[0] = 2;

注意,初始化weak_ptr需要用到shared_ptr。

代码样例:

auto sh_p = make_shared<int>(40);
weak_ptr<int> wp1(sh_p);
weak_ptr<int> wp2 = sh_p;
2.智能指针的解引用

智能指针的解引用操作与原始指针类似,可以调用"*"或"->"对智能指针进行解引用,访问分配到的堆内存地址。

但是weak_ptr不提供指针的解引用操作,即无法调用"*"或"->"获得weak_ptr所指向的变量。

完整C++代码实现:

#include <iostream>
#include <memory>
int main() {
    struct C {
        int a=1;
        int b=2;
    };
    std::shared_ptr<C> p1(new C);
    std::unique_ptr<int> p2(new int(40));
    std::shared_ptr<int> p3 = std::make_shared<int>(15);
    std::unique_ptr<int> p4 = std::make_unique<int>(10);
    std::weak_ptr<int> p5 = p3;
    std::cout << p1->a << std::endl;
    std::cout << p1->b << std::endl;
    std::cout << *p2 << std::endl;
    std::cout << *p3 << std::endl;
    std::cout << *p4 << std::endl;
    std::cout << *p5.lock() << std::endl;
    return 0;
}

运行结果:

1
2
40
15
10
15

3.unique_ptr智能指针

常用的成员函数:

get(): 返回指向变量的原始指针。

reset(): 重置智能指针,使它所持有的资源为空。

swap(): 交换两个智能指针所管理的资源。

release(): 返回指向变量的原始指针,并释放所有权。

用法说明:

reset()让unique_ptr重新指向给定的指针。如果unique_ptr不为空,它原先占有的内存资源将被释放。

由于一个初始化后的unique_ptr独占了它所指向的变量,因此unique_ptr不支持普通的拷贝或赋值操作。

虽然不能拷贝或赋值unique_ptr,但可以通过调用release()/reset()函数将指针的所有权转移给另一个unique_ptr。

4.shared_ptr智能指针

常用的成员函数:

get(): 返回指向变量的原始指针。

reset(): 重置智能指针,使它所持有的资源为空。

swap(): 交换两个智能指针所管理的资源。

use_count(): 返回智能指针所指向变量的被引用数量。

unique(): 检查所指向的变量是否仅由当前shared_ptr的实例管理。

用法说明:

shared_ptr允许多个指针指向同一块堆内存。

shared_ptr提供了引用计数,监视当前变量正在被多少个指针实例所引用。

由于shared_ptr存在引用计数,仅在最后一个引用被销毁或重置时,该智能指针才会释放持有的内存资源。。

shared_ptr可被以下函数强制转换:

const_pointer_cast()

dynamic_pointer_cast()

static_pointer_cast()

reinterpret_pointer_cast() (C++17标准引入)

如图所示,指针p1、p2指向同一块内存地址。

5.weak_ptr智能指针

常用的成员函数:

reset(): 重置智能指针,使它所持有的资源为空。

swap(): 交换两个智能指针所管理的资源。

expired(): 检查weak_ptr所指向的资源是否有效,返回true的时候,垃圾回收进程就会清除该指针所指向的内存资源。

use_count(): 返回智能指针所指向shared_ptr的数量。

lock(): 获取weak_ptr所指向的shared_ptr实例。

用法说明:

weak_ptr不占有内存资源,但是可以指向由shared_ptr管理的内存资源。

当weak_ptr指向shared_ptr时,是弱共享shared_ptr,并不会使shared_ptr的引用计数增加。

weak_ptr的出现可以帮助开发者解决智能指针使用期间发生的"循环引用"问题。

完整C++代码实现:

#include <iostream>
#include <memory>
using namespace std;

void Check(weak_ptr<int>& wp) {
    shared_ptr<int> sp = wp.lock(); //获得shared_ptr<int>实例
    if (sp != nullptr)
        cout << "still " << *sp << endl;
    else
        cout << "pointer is invalid." << endl;
}

int main() {
    shared_ptr<int> sp1(new int(40));
    shared_ptr<int> sp2 = sp1;
    weak_ptr<int> wp = sp1; //wp指向shared_ptr<int>实例
    cout << *sp1 << endl;
    cout << *sp2 << endl;
    cout << sp1.use_count() << endl;
    cout << sp2.use_count() << endl;
    Check(wp);
    sp1.reset();
    /*
    cout << sp1 << endl;  
    返回:00000000
    */
    cout << *sp2 << endl;
    cout << sp1.use_count() << endl;
    cout << sp2.use_count() << endl;
    cout << sp2.unique() << endl;
    Check(wp);
    sp2.reset();
    cout << sp2.use_count() << endl;
    Check(wp);
    return 0;
}

运行结果:

40
40
2
2
still 40
40
0
1
1
still 40
0
pointer is invalid.

6.智能指针的复制和移动

unique_ptr不支持复制、赋值等操作,它只能被移动,而移动操作经常借助std::move函数来实现。

std::move可以把一个智能指针所占有的资源转移给另一个智能指针。

shared_ptr包含一个显式的构造函数,可用于将右值unique_ptr转换为shared_ptr。转换成功以后,shared_ptr将接管unique_ptr所占有的所有资源。因此,如果unique_ptr为右值(可以粗略理解为,位于赋值符号的右边)的时候,可以将其赋值给shared_ptr。

复制操作会让shared_ptr所指向变量的引用计数加1,而移动操作不会让shared_ptr所指向变量的引用计数加1。

1.unique_ptr转shared_ptr代码样例:

std::shared_ptr<int> p1 = std::make_unique<int>(66);

完整C++代码实现:

#include <iostream>
#include <memory>
int main() {
    std::shared_ptr<int> p1 = std::make_unique<int>(66);
    std::cout << *p1 << std::endl;
    std::cout << p1.use_count() << std::endl;
    return 0;
}

运行结果:

66
1

2.移动操作代码样例:

//p1指向该int对象
shared_ptr<int> p1(new int(40));

//移动构造p2
//p1指向的内存为空,p2指向该int对象
shared_ptr<int> p2(std::move(p1));

//p2指向的内存为空,p3指向该int对象
shared_ptr<int> p3;
p3 = std::move(p2);

//整个过程中,int对象的引用计数保持在1

完整C++代码实现:

#include <iostream>
#include <memory>
int main() {
    std::shared_ptr<int> p1(new int(40));
    std::shared_ptr<int> p2(std::move(p1));
    std::shared_ptr<int> p3;
    p3 = std::move(p2);
    std::cout << p3.use_count() << std::endl;
    std::shared_ptr<int> p4;
    std::shared_ptr<int> p5;
    p4 = p3;
    p5 = p3;
    std::cout << p3.use_count() << std::endl;
    p3.reset();
    p3 = std::make_unique<int>(10);
    std::cout << p3.use_count() << std::endl;
    return 0;
}

运行结果:

1
3
1

7.关于new/delete的补充说明

显式地调用new和delete不仅会增加代码的复杂度,还会引发内存泄露等风险。

如果必须要使用new/delete,可以考虑以下措施来避免:

1.尽可能使用栈内存

栈内存不会造成内存泄露,且资源一旦超出栈的使用范围就会被销毁。

2.使用make functions在堆上分配资源

例如,使用std::make_unique<T>或std::make_shared<T>来实例化资源,然后将它包装成一个资源管理对象去管理资源以及智能指针。

3.尽量使用容器(标准库中的容器,Boost中的容器等)

容器会对其元素进行存储空间的管理,这些官方容器都实现了自己的内存管理逻辑,避免内存出问题。

至于为何要采用上述规则,后面会新增动态内存分配和右值引用等相关章节来详细说明。

二,参考阅读

《C++ Primer》

《C++代码整洁之道》

《C++高级编程》

《C++新经典》

https://www.apiref.com/cpp-zh/cpp/memory.html

https://learn.microsoft.com/zh-cn/cpp/cpp/smart-pointers-modern-cpp?view=msvc-170

标签:std,C++,智能,shared,unique,ptr,指针
From: https://blog.csdn.net/CoderZZ_2024/article/details/136792915

相关文章

  • Optional避免空指针
    1/**2*<h1>学会Optional,规避空指针异常</h1>3**/4@SuppressWarnings("all")5publicclassOptionalUsage{67privatestaticvoidbadUsageOptional(){89Optional<User>optional=Optional.ofNullable(nu......
  • 打造个人版ChatGPT:人工智能对话的探索之旅
    大家好,在这里想跟大家分享一个近期我倾心打造的小项目——一款基于先进人工智能技术的对话式交互网站,可以说是“我的ChatGPT”。这款网站旨在通过模拟人类智能对话的方式,为用户提供个性化的信息查询、问题解答及创意启发等功能。点击这里进入自踏入编程世界以来,我一直对人工智能......
  • AcWing 1230. K倍区间 C++满分题解
    原题链接https://www.acwing.com/problem/content/1232/题目分析求区间和,我们可以通过前缀和来求出。我们规定sum[i]表示第1个元素到第i个元素的和。那么sum[r]-sum[l-1]就是区间[l,r]的和。一维前缀和for(inti=1;i<=n;i++){scanf("%lld",&sum[i]);......
  • 深入理解指针
    1、内存和地址1.1内存在生活中相当于一栋楼中房间号,在计算机中cpu,在处理数据时,需要的数据就是在内存中读取的,处理以后的数据也会放在内存中,内存会被划分成一个一个的内存单元,每个内存单元的大小取一个字节一个比特位可以存储一个2进制的为1或0;bit——比特位;Byte——字节;1B......
  • 限流器(流控)+ 线程 C++实现
    在C++中,你可以使用互斥锁(mutex)和条件变量(conditionvariable)来实现一个简单的限流器(流控)以及线程。下面是一个简单的例子,它创建了一个限流器类,该类允许一定数量的线程同时访问某个资源。#include<iostream>#include<thread>#include<mutex>#include<condition_variable>......
  • C++反射
    反射教程让程序看到自己的数据,并且能够对数据进行操作类型萃取对类型做萃取,有一组混合类型,将特定类型获取出来核心思路:使用模板来匹配查找例子:指针类型萃取解除一层指针,三级变二级,二级变一级template<typenameT>structremove_pointer{};template<typenameT>stru......
  • C++版数据结构与算法
    大家好,今天开始给大家每天带来C++版的数据结构与算法,后面也会包括C#的系统学习。这段代码是一个C++实现的排序算法集合。其中包括选择排序(selectionsort)、冒泡排序(bubblesort)、插入排序(insertionsort)和归并排序(mergesort)。算法后越往后越难,此次做这个系列博客,是想从......
  • 23种设计模式核心思想及代码实现(Java C++)
    目录代码OOP七大原则策略模式单例模式观察者模式装饰模式抽象工厂模式工厂模式简单工厂模式工厂模式抽象工厂模式三种工厂模式的区别简单工厂模式和策略模式的不同pipeline模式职责链模式代理模式静态代理动态代理......
  • 语音转文字——sherpa ncnn语音识别离线部署C++实现
    简介Sherpa是一个中文语音识别的项目,使用了PyTorch进行语音识别模型的训练,然后训练好的模型导出成torchscript格式,以便在C++环境中进行推理。尽管PyTorch在CPU和GPU上有良好的支持,但它可能对资源的要求较高,不太适合嵌入式环境或要求轻量级依赖的场景。考虑到模......
  • C++ this指针
    1. this指针的用处一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时......