C++智能指针初学常用细节

本文介绍了C++中智能指针的基本概念和使用,包括shared_ptr的引用计数、内存分配,unique_ptr的独占特点,以及weak_ptr的弱引用特性。内容涵盖动态内存管理的注意事项,智能指针的优点,以及各种智能指针的定义、使用场景和陷阱。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:智能指针部分的知识实在是太多了,好多东西我也不是很理解,限于篇幅和我个人能力,我这篇文章只能总结部分有关问题,更多知识点还需要去看c++ primer这本书的相关内容,另外auto_ptr由于已经过时,本文也不会提及。


在c++中动态内存的管理是用一对运算符完成的:new和delete
new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针。
delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。
在c语言中,程序用堆来存储动态分配的对象即那些在程序运行时分配的对象,当动态对象不再使用时,我们的代码必须显式的销毁它们。

而这样的话使用普通指针动态内存管理,就会有好多缺点

1.容易忘记释放,会造成内存泄漏;
2.已经在一个地方释放,又在另一个地方重新释放;
3.使用已经释放的内存,就会产生引用非法内存的指针。
4.调用者要自己通过delete释放内存;	

在c++中的用普通指针管理动态内存的注意事项

    int *pi1 = new int;//如果分配失败,new跑出std::bad::alloc
    int *pi2 = new(nothrow) int; //如果new分配失败,new返回一个空指针

    Foo obj;
    auto p1 = new auto(obj); //p指向一个与obj类型相同的对象,该对象用obj进行初始化
    /* auto p2 = new auto(1,2,3);//错误,括号中只能有单个初始化器 */

上段代码中Foo的源码

//一个函数,返回一个shared_ptr,指向一个foo类型的动态分配的对象
//对象是通过一个类型为T的参数初始化的
class Foo
{
public:
    const int test;
    //const成员必须使用列表初始化
    Foo():test(0){};
    //如果不是const,定义了自己使用的构造函数,想继续使用默认的构造函数,加下面的代码
    /* Foo() = default; */
    Foo(int i):test(i){}
};

因此在c++中,为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。

智能指针的优点

就算程序块提前结束,智能指针也内存在不需要时进行释放。

智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。标准库提供的两种智能指针的区别在于管理底层指针的方法不同。

  1. shared_ptr允许多个指针指向同一个对象。
  2. unique_ptr则“独占”所指向的对象。
  3. 标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。

那么之后我就来为大家介绍一下智能指针。


(一)shared_ptr

简单介绍

shaped_ptr有一个关联的计数器,每当拷贝一个智能指针的时候,计数器就会增加1,如果我们给一个shared_ptr赋予一个新值的时候,就把的计数器数值减1,或者 一个shared_ptr 离开了原先的作用域,也就是该shared_ptr被销毁的时候,计数器也会减1。

基本操作意义
shared_ptr p空智能指针,可以指向类型为 T 的对象
p将 p 用作一个条件判断,若p指向一个对象,则为true
*p解引用p,获得它指向的对象
swap(p,q)交换 p 和 q 中的指针
p.unique()若p.use_count() 为1,返回true,否则返回 false
p.use_count()返回与p共享的智能指针的数量
p.get()返回 p 总保存的指针

这里附上一段代码是在函数中关于引用计数的问题,这里就和c指针很像了。


template <typename T>
shared_ptr <Foo> factory(T argg){
    //恰当的处理
    //share_ptr负责释放内存
    return make_shared<Foo>(argg);
}

template <typename T>
shared_ptr <Foo> use_factory(T argg){
    shared_ptr<Foo> p = factory(argg); 
    //使用p
    return p; //返回p,引用计数+1,对于代码来说,这个像是
    //p离开了作用域,但是由于引用计数+1,所以不会释放内存,有点像指针
}

template <typename T>
void use_factory(T argg){
    shared_ptr<Foo> p = factory(argg); 
    //使用p
    //离开作用域,整个就结束了
}

定义一个shared_ptr

 shared_ptr<int> p5 = make_shared<int>();
    //p5是一个指向值初始化的int,初始值0
    
    /* vector<string>(2); */
    auto r = make_shared<int>(42);
    r = p5;

shaped_ptr的特性

(图源网络,侵删,其实就是c++ primer上面的内容)
在这里插入图片描述

关于shaped_ptr的内存分配

可以使用make_shared函数进行内存分配,是最安全的分配方法,和使用动态内存的方法就是调用一个名为make_shared的标准库函数,此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。

 //最安全的分配和动态使用内存的方法
    shared_ptr<int> p3 = make_shared<int>(42);
    //指向了一个42int的shape——ptr
    shared_ptr<string> p4 = make_shared<string>(10,'9');
    //指向了一个“99999999”的string
    shared_ptr<int> p5 = make_shared<int>();
    //p5是一个指向值初始化的int,初始值0

当然,shared_ptr也可以和和new的结合使用

    //由于智能指针的构造函数是explicit的
    //所以只能使用直接初始化
    /* shared_ptr<int>p1 = new int(1024);//错的 */
    shared_ptr<int> p2(new int(42));
    /*--------------------------------------------------*/
    //shared_ptr的拷贝
	/* shared_ptr<int> clone(int p){ */
	/*     return new int(p); //隐式的转换为shared——ptr,错误 */
	/* } */
	shared_ptr<int> clone(int p){
	    return shared_ptr<int>(new int(p)); //显式的转换为shared——ptr,正确
	}

shared_ptr的常见使用之一

    p2.reset(new int(1024));
    //p2指向新对象
    if(!p2.unique())//获取是不是唯一引用计数
        p2.reset(new int(3212));//不是可以重新绑定
    else
        *p2 += 12;//是,可以更改值了

shared_ptr的规范处理

    //1.不使用相同的内置指针初始化(或reset)多个智能指针
    //2.不delete get()返回指针
    //不要使用get初始化另一个智能指针或者为智能指针赋值
    //get函数返回一个内置指针,但是使用get()返回的指针不能delete此指针,不然这块内存会二次释放
    shared_ptr<int> p (new int(42));
    int *qq = p.get();
    //使用q不要让他管理的指针被释放掉
    {
        //新程序块
        //未定义:两个队里的shared_ptr指向相同的内存
        shared_ptr<int> (q);
        //程序块结束,q被销毁,它指向的内存被释放
    }
    int fooo = *p;//未定义,内存已被释放
    //3.如果使用了智能指针管理的资源不是new分配的资源,记住要穿进去一个删除器

shaped_ptr总结与陷阱

总结:
auto r = make_shared<int>(42);
r = p5;
//r指向的int只有一个引用计数
//递减r原来指向对象的引用计数
//r原来指向的对象已没有引用者,会自动释放
//他是通过析构函数去完成销毁工作的、
//shared_ptr的析构函数会销毁对象,并释放他占用的内存
//如果不使用shared_ptr,记得删除,不然还是会占用内存
智能指针陷阱: 
(1)不使用相同的内置指针值初始化(或reset)多个智能指针。 
(2)不delete get()返回的指针 
(3)不使用get()初始化或reset另一个智能指针 
(4)如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了 
(5)如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器

(二)unique_ptr

简单介绍

unique_ptr拥有他所指的对象,当一个unique_ptr被释放,那么他指的那块内存也就被释放了。
(图源网络,侵删,其实就是c++ primer上面的内容)
在这里插入图片描述

定义一个unique_ptr

  unique_ptr<double> px;///可以定义一个指向double的unique_ptr

与shared_ptr的相同点

在这里插入图片描述

unique_ptr特性

  1. 定义一个unique_ptr,某个时刻只能有一个unique_ptr指定一个给定对象
  2. 当定义一个unique_ptr绑定到一个new返回的指针上,他没有类似make_shared的标准库函数返回一个unigue_ptr
  3. unique_ptr不支持赋值和拷贝
  4. 管理删除器的方式不同,unique_ptr需要相比较
  5. shared_ptr可以放到容器vector里面,而unique_ptr不可以

以下是代码实践

    unique_ptr<double> px;///可以定义一个指向double的unique_ptr
    /* px = new double(12);//不支持赋值 */
    /* unique_ptr<string> py(px);//不支持拷贝 */
    /* px = py;//不支持赋值 */
    unique_ptr<int> py(new int(42));
    py = nullptr;//释放,指向为空
    unique_ptr<int> pz(py.release());//release将py的指针置为空
    unique_ptr<int> pi(new int(42));
    //将所有权从pi转移给pz
    /* pi.release(); //错,pi不会释放内存,而且我们会丢了指针 */
    //不能拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非const)
    //unique_ptr转移给另一个unique
    pz.reset(pi.release());//reset释放了pz之前指的资源,pi转移给pz
    cout<<*pz<<endl;

    //不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销
    //毁的unique_ptr,最常见的例子是从函数返回一个unique_ptr


	//unique_ptr的拷贝
	unique_ptr<int> clone1(int p){
	    //正确:从int*创建一个unique_ptr<int>
	    return unique_ptr<int>(new int(p));
	}
	
	//还可以返回一个局部对象的拷贝
	unique_ptr<int> clone2(int p){
	    //正确:从int*创建一个unique_ptr<int>
	     unique_ptr<int> ptr(new int(p));
	     return ptr;
	}
	//由于编译器知道返回的对象将要被销毁,编译器执行一种特殊的拷贝,移动构造



    vector<shared_ptr<int>> v;//避免使用匿名的临时的shard_ptr
    shared_ptr<int> sha(new int);
    v.push_back(sha);//内部也要在构造一次
    cout<<sha.use_count()<<endl;//所以use_count等于2

    vector<unique_ptr<int>> v1;
    unique_ptr<int> u(new int);

    /* v1.push_back(u); */
    //unique_ptr不能放进去,vector是const的,但是unique重载的运算符=是非const的,是需要把之前的值释放的
    /* cout<<u.get()<<endl; */

(三) weak_ptr

简单介绍

  1. weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象,不会单独存在,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数,c++利用这种弱引用可以做一些特殊的工作。
  2. 一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。
  3. 使用weak_ptr访问数据时,必须要将他的访问权升级成shared_ptr,才可以使用,通过调用lock函数实现。

在这里插入图片描述

定义一个weak_ptr

  weak_ptr<double> p;;///可以定义一个指向double的weak_ptr

weak_ptr的常见使用之一

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

class x
{
public:
    x()
    {
        cout<<"xx"<<endl;
    }
    ~x()
    {
        cout<<"~xx"<<endl;
    }
};
int main(){
    weak_ptr<x> p;

    {
        shared_ptr<x> p2(new x);
        cout<<p2.use_count()<<endl;
        p = p2;
        cout<<p2.use_count()<<endl;

        shared_ptr<x> p3 = p.lock();
        if(!p3)
        {
            cout<<"object is destory"<<endl;
        }
        else
        {
            cout<<"object is not destory"<<endl;
        }
    }

    shared_ptr<x>p4 = p.lock();
    if(!p4)
    {
        cout<<"object is destory"<<endl;
    }
    else
    {
        cout<<"object is not destory"<<endl;
    }

    return 0;
}

weak_ptr的使用注意事项

 //weak_ptr是一种不控制指针生存周期的智能指针
    //创建weak_ptr需要用一个shared_ptr,把一个weak_ptr绑定到shared_ptr上
    //shared_ptr不进行引用计数的增长
    
    auto pp = make_shared<int>(42);
    weak_ptr<int>wp(pp);//弱共享,没有增加引用计数

    //如果最后一个shared_ptr被释放,那么weak_ptr指向也就不存在了
    //所以每次在用的时候需要先判断一下
    
    if(shared_ptr<int> np = wp.lock()){//如果np不为空,则条件成立
        printf("还在\n");
    }
    else 
        printf("没了\n");



个人学习智能指针的完整代码,顺便还有关于顶层底层const的一些解释

#include <bits/stdc++.h>
using namespace std;
//使用动态内存的三种原因
//1.程序不清楚自己需要多少对象
//2.程序不清楚所需对象的准确类型
//3.程序需要在多个对象之间共享数据

//智能指针的优点
//就算程序块提前结束,智能指针也内存在不需要时进行释放


//shared_ptr的拷贝
/* shared_ptr<int> clone(int p){ */
/*     return new int(p); //隐式的转换为shared——ptr,错误 */
/* } */
shared_ptr<int> clone(int p){
    return shared_ptr<int>(new int(p)); //显式的转换为shared——ptr,正确
}

//unique_ptr的拷贝
unique_ptr<int> clone1(int p){
    //正确:从int*创建一个unique_ptr<int>
    return unique_ptr<int>(new int(p));
}

//还可以返回一个局部对象的拷贝
unique_ptr<int> clone2(int p){
    //正确:从int*创建一个unique_ptr<int>
     unique_ptr<int> ptr(new int(p));
     return ptr;
}
//由于编译器知道返回的对象将要被销毁,编译器执行一种特殊的拷贝,移动构造



//一个函数,返回一个shared_ptr,指向一个foo类型的动态分配的对象
//对象是通过一个类型为T的参数初始化的
class Foo
{
public:
    const int test;
    //const成员必须使用列表初始化
    Foo():test(0){};
    //如果不是const,定义了自己使用的构造函数,想继续使用默认的构造函数,加下面的代码
    /* Foo() = default; */
    Foo(int i):test(i){}
};
template <typename T>
shared_ptr <Foo> factory(T argg){
    //恰当的处理
    //share_ptr负责释放内存
    return make_shared<Foo>(argg);
}

template <typename T>
shared_ptr <Foo> use_factory(T argg){
    shared_ptr<Foo> p = factory(argg); 
    //使用p
    return p; //返回p,引用计数+1,对于代码来说,这个像是
    //p离开了作用域,但是由于引用计数+1,所以不会释放内存,有点想指针
}

template <typename T>
void use_factory(T argg){
    shared_ptr<Foo> p = factory(argg); 
    //使用p
    //离开作用域,整个就结束了
}
int main(){
    //普通指针
    //缺点,调用者要自己通过delete释放内存
    //1.容易忘记释放
    //2.已经在一个地方释放,又在另一个地方重新释放
    //3.使用已经释放的内存
    //
    //
    int *pi1 = new int;//如果分配失败,new跑出std::bad::alloc
    int *pi2 = new(nothrow) int; //如果new分配失败,new返回一个空指针

    Foo obj;
    auto p1 = new auto(obj); //p指向一个与obj类型相同的对象,该对象用obj进行初始化
    /* auto p2 = new auto(1,2,3);//错误,括号中只能有单个初始化器 */

    const int *pci = new const int (1024);
    //分配并初始化一个const int为1024,出于变量初始化相同的原因,对动态分配的对象最好进行初始化
    const string * pcs = new const string;//底层层const不准改里面值了,可以该指向
    string * const pcsi = new string;//顶层const不准该指向了,可以改值

    string a = "dasdsa";
    /* *pcs = "dasdsa"; */ //底层const,可以改指向不能改值
    pcs = &a;           //该指向
    *pcsi = "dasdsadas";//改值
    /* pcsi = &a; */ //顶层const,不允许改变指向

    //最安全的分配和动态使用内存的方法
    shared_ptr<int> p3 = make_shared<int>(42);
    //指向了一个42int的shape——ptr
    shared_ptr<string> p4 = make_shared<string>(10,'9');
    //指向了一个“99999999”的string
    shared_ptr<int> p5 = make_shared<int>();
    //p5是一个指向值初始化的int,初始值0
    
    /* vector<string>(2); */
    auto r = make_shared<int>(42);
    r = p5;

    /* shared_ptr 和new的结合使用 */
    //由于智能指针的构造函数是explicit的
    //所以只能使用直接初始化
    /* shared_ptr<int>p1 = new int(1024);//错的 */
    shared_ptr<int> p2(new int(42));
    p2.reset(new int(1024));
    //p2指向新对象
    if(!p2.unique())//获取是不是唯一引用计数
        p2.reset(new int(3212));//不是可以重新绑定
    else
        *p2 += 12;//是,可以更改值了
    
    //智能指针的规范处理
    //1.不使用相同的内置指针初始化(或reset)多个智能指针
    //2.不delete get()返回指针
    //不要使用get初始化另一个智能指针或者为智能指针赋值
    //get函数返回一个内置指针,但是使用get()返回的指针不能delete此指针,不然这块内存会二次释放
    shared_ptr<int> p (new int(42));
    int *qq = p.get();
    //使用q不要让他管理的指针被释放掉
    {
        //新程序块
        //未定义:两个队里的shared_ptr指向相同的内存
        /* shared_ptr<int> (q); */
        //程序块结束,q被销毁,它指向的内存被释放
    }
    int fooo = *p;//未定义,内存已被释放
    //3.如果使用了智能指针管理的资源不是new分配的资源,记住要穿进去一个删除器
    
    //总结
    //r指向的int只有一个引用计数
    //递减r原来指向对象的引用计数
    //r原来指向的对象已没有引用者,会自动释放
    //他是通过析构函数去完成销毁工作的、
    //shared_ptr的析构函数会销毁对象,并释放他占用的内存
    //如果不使用shared_ptr,记得删除,不然还是会占用内存


    //unique_ptr拥有他所指的对象,当一个unique_ptr被释放,那么他就被释放了
    //与shaped_ptr的不同
    //1.定义一个unique_ptr,某个时刻只能有一个unique_ptr指定一个给定对象
    //2.当定义一个unique_ptr绑定到一个new返回的指针上,他没有类似make_shared的标准库函数返回一个unigue_ptr
    //3.unique_ptr不支持赋值和拷贝
    //4.管理删除器的方式不同,unique_ptr需要相比较
    
    unique_ptr<double> px;///可以定义一个指向double的unique_ptr
    /* px = new double(12);//不支持赋值 */
    /* unique_ptr<string> py(px);//不支持拷贝 */
    /* px = py;//不支持赋值 */
    unique_ptr<int> py(new int(42));
    py = nullptr;//释放,指向为空
    unique_ptr<int> pz(py.release());//release将py的指针置为空
    unique_ptr<int> pi(new int(42));
    //将所有权从pi转移给pz
    /* pi.release(); //错,pi不会释放内存,而且我们会丢了指针 */
    //不能拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非const)
    //unique_ptr转移给另一个unique
    pz.reset(pi.release());//reset释放了pz之前指的资源,pi转移给pz
    cout<<*pz<<endl;

    //不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销
    //毁的unique_ptr,最常见的例子是从函数返回一个unique_ptr
    //见函数

    //重载一个unique_ptr中的删除器会影响到unique_ptr类型以及如何构造(reset)
    //该类型的对象,所以我们必须在尖括号中unique_ptr指向类型之后提供删除器类型
    
    
    //p指向一个类型为objT的对象,并使用一个类型delT的对象释放objT对象
    //它会调用一个名为fcn的delT类型的对象
    /* unique_ptr<obj,delT>p(new objT,fcn); */

    //重连例子
    //shared_ptr和unique_ptr两个

    //weak_ptr是一种不控制指针生存周期的智能指针
    //创建weak_ptr需要用一个shared_ptr,把一个weak_ptr绑定到shared_ptr上
    //shared_ptr不进行引用计数的增长
    
    auto pp = make_shared<int>(42);
    weak_ptr<int>wp(pp);//弱共享,没有增加引用计数

    //如果最后一个shared_ptr被释放,那么weak_ptr指向也就不存在了
    //所以每次在用的时候需要先判断一下
    
    if(shared_ptr<int> np = wp.lock()){//如果np不为空,则条件成立
        printf("还在\n");
    }
    else 
        printf("没了\n");


    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Randy__Lambert

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值