C++智能指针

发布时间:2024年01月22日

普通指针不足

  • new和new[] 的内存需要用delete和delete[]释放
  • 程序员的主观失误,忘了或漏了释放
  • 程序员也不知道何时释放内存

智能指针设计思路

  • 智能指针是类模板,在栈上创建智能指针对象
  • 把普通指针交给智能指针对象
  • 智能指针对象过期时,调用析构函数释放普通指针的内存

智能指针类型

  • auto_ptr C+++98标准 但是C++17已经弃用
  • unique_ptr、shared_ptr和weak_ptr是C++11的标准

unique_ptr

概念

指独享指向的对象。
同一时间只有一个unique_ptr指向同一个对象,当unique_ptr被销毁时,该对象也被销毁

用法

包含 memory 头文件
在这里插入图片描述

初始化

    //方法一
    unique_ptr<CBB>pp(new CBB("貂蝉",20));//分配内存并初始化

    //方法二 C++14标准
    unique_ptr<CBB>p0= std::make_unique<CBB>("王昭君",21);

    //方法三
    CBB *p = new CBB("西施",18);
    //假设我们忘记delete指针p了 那么不会调用析构函数
    unique_ptr<CBB>pt(p);//我们使用unique_ptr智能指针管理p 用已经存在的地址初始化

使用

  • 重载了->和*操作符,可以像使用指针一样使用unique_ptr
  • 不支持普通的拷贝和赋值
  • 不要用同一个裸指针(普通指针)初始化多个unique_ptr指针(会造成同一块内存多次释放,操作野指针)
  • get方法返回裸指针
    cout<<"裸指针地址:"<<pp.get()<<endl;
    cout<<"pp的值:"<<pp<<endl;
    cout<<"pp的地址:"<<&pp<<endl;
/*
裸指针地址:0x2be260c62d0
pp的值:0x2be260c62d0
pp的地址:0xff023ff808
*/
  • 不要用unique_ptr管理不是new分配的内存
  • 用于函数参数
    • 传引用(不能传值,因为没有拷贝构造函数)
    • 裸指针
  • 不支持指针的运算(+、-、++、–)

更多技巧

  • 1)将一个 unique ptr 赋给另一个时,如果源 unique_ptr 是一个临时右值,编译器允许这样做;如果源 unique ptr 将存在一段时间,编译器禁止这样做。一般用于函数的返回值
class CBB{
public:
    int cbb;
    string m_name;
    CBB(){
        cout<<"默认构造函数"<<endl;
    }
    CBB(string name,int num):m_name(name),cbb(num){
        cout<<"带参数的构造函数"<<name<<endl;
    }
    ~CBB(){
        cout<<"析构函数"<<endl;
    }
};

unique_ptr<CBB> fun(){
    unique_ptr<CBB>p(new CBB("小乔",100));
    return p;
}

    unique_ptr<CBB>p0(new CBB("貂蝉",20));
    unique_ptr<CBB>p1;
    // p1=p0;//报错 这样不可以 因为p0还存在
    // 匿名对象赋值  允许 匿名对象仅存活在该语句,是临时的
    p1= unique_ptr<CBB>(new CBB("妲己",2100));

    cout<<"fun之前"<<endl;
    //本身p1管理这name为妲己的CBB对象,但是此时p1管理fun返回的CBB对象,那么赋值完毕之后要释放
    p1=fun();//这里能接受的原因时fun返回的是临时对象,如果返回的是全局变量那么肯定报错
    cout<<"fun之后"<<endl;

/*
带参数的构造函数貂蝉
带参数的构造函数妲己
fun之前
带参数的构造函数小乔
析构函数
fun之后
析构函数
析构函数
*/
  • 2)用 nullptr给 unique_ptr 赋值将释放对象,空的 unique_ptr==nullptr
    unique_ptr<CBB>p0(new CBB("貂蝉",20));//分配内存并初始化
    p0= nullptr;
    cout<<"p0=nullptr"<<endl;
/*
带参数的构造函数貂蝉
析构函数
p0=nullptr
*/
//析构函数先于最后一个调试语句输出,说明给智能指针赋值nullptr后就释放了对象
  • 3)release()释放对原始指针的控制权,将 unique_ptr 置为空,返回裸指针。(可用于把 unique_ptr传递给子函数,子函数将负责释放对象)
  • 4)std:.move()可以转移对原始指针的控制权。(可用于把 unique ptr 传递给子函数,子函数形参
    也是 unique ptr)
void func1(const CBB*a){
    cout<<a->m_name<<endl;
}
void func2(CBB*a){
    cout<<a->m_name<<endl;
    delete a;
}
void func3(unique_ptr<CBB>&c){
    cout<<c->m_name<<endl;
}
void func4(unique_ptr<CBB>c){
    cout<<c->m_name<<endl;
}
int main() {

    //func1 2 3 4要一个一个调用
    unique_ptr<CBB>pu(new CBB("貂蝉",20));//分配内存并初始化

    cout<<"开始调用函数"<<endl;
    func1(pu.get()); //函数需要一个裸指针,但是不对裸指针负责(不释放)
    /*
    开始调用函数
    貂蝉
    调用函数完成
    析构函数
    */
    func2(pu.release());//函数需要一个裸指针,并且对裸指针负责(释放)
    /*
     开始调用函数
    貂蝉
    析构函数
    调用函数完成
      */

    func3(pu);//函数需要一个unique_ptr指针,但是不对裸指针负责(不释放)
    /*
    开始调用函数
    貂蝉
    调用函数完成
    析构函数
     * */

    //move将对裸指针的控制权交给函数形参
    func4(std::move(pu));//函数需要一个unique_ptr指针,并且对裸指针负责(释放)
    /*
    开始调用函数
    貂蝉
    析构函数
    调用函数完成
     */
    cout<<"调用函数完成"<<endl;

  • 5)reset()释放对象
void reset(T*_ptr=(T*)nullptr);

p.reset();//释放p指向的资源对象
p.reset(nullptr);//释放p指向的资源对象
p.reset(new AA("a"));//释放p指向的资源对象,同时指向新的对象
  • 6)swap()交换两个 unique_ptr 的控制权。
  • 7)unique_ptr 也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样
  • 8)unique_ptr不是绝对安全的,如果调用exit()退出,全局的unique_ptr指针可以自动释放,但是局部的unique_ptr指针不可以自动释放
  • 9)unique_ptr提供了支持数组的具体化版本
unique_ptr<int[]>p(new int[3]);//不指定初始值
unique_ptr<int[]>p (new int[3]{1,2,3});//指定初始值

shared_ptr

概念

共享它指向的对象
多个shared ptr 可以指向(关联)相同的对象,在内部采用计数机制来实现
当新的shared_ptr与对象关联时,引用计数器+1
当shared_ptr超出作用域时,引用计数器-1
引用计数器=0,没有任何的shared_ptr与对象关联,释放该对象

用法

shared_ptr的构造函数也是explicit,但是没有删除拷贝构造和赋值函数

初始化

    //方法一
    shared_ptr<CBB>pp(new CBB("貂蝉",20));//分配内存并初始化

    //方法二 C++11标准
    shared_ptr<CBB>p0= std::make_shared<CBB>("王昭君",21);

    //方法三
    CBB *p = new CBB("西施",18);
    shared_ptr<CBB>pt(p);//用已经存在的地址初始化

	//方法四
	CBB *p = new CBB("西施",18);
    shared_ptr<CBB>pt(p);//用已经存在的地址初始化
    shared_ptr<CBB>p1 = pt;
    shared_ptr<CBB>p2(pt);

使用

  • 重载了->和*操作符,可以像使用指针一样使用unique_ptr
  • use_count方法返回引用计数器的值
  • unique()方法,如果 use count()为1,返回 true,否则返回 false
  • 支持普通的拷贝和赋值,左值的 shared_ptr 的计数器将减1,右值 shared_ptr 的计算器将加 1
  • 不要用同一个裸指针(普通指针)初始化多个shared_ptr指针(会造成同一块内存多次释放,操作野指针)
  • get方法返回裸指针
  • 不要用shared_ptr管理不是new分配的内存

更多技巧

  • 1)用 nullptr给 shared_ptr 赋值将把计数器-1
  • 2)std:.move()可以转移对原始指针的控制权。还可以把unique_ptr转移成shared_ptr 反过来不行
  • 3)reset
  • 4)swap
  • 5)shared_ptr ** 也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质**,如同使用裸指针管理基类对象和派生类对象那样
  • 6)shared_ptr 不是绝对安全的,如果调用exit()退出,全局的shared_ptr 指针可以自动释放,但是局部的shared_ptr 指针不可以自动释放
  • 7)shared_ptr 提供了支持数组的具体化版本
  • 8)shared_ptr 的引用计数本身就是线程安全(引用计数是原子操作)
    在这里插入图片描述
  • 9)如果 unique_ptr 能解决问题,就不要用 shared ptr。unique ptr 的效率更高,占用的资源更少

智能指针删除器

在默认情况下,智能指针过期的时候,用 delete 原始指针;释放它管理的资源

程序员可以自定义删除器,改变智能指针释放资源的行为。

删除器可以是全局函数、仿函数和 Lambda 表达式,形参为原始指针(裸指针)

void deletefunc(CBB*b){
    cout<<"自定义删除器(全局函数)"<<endl;
    delete b;
}
class deleteclass{
public:
    void operator()(CBB*p){
        cout<<"自定义删除器(仿函数)"<<endl;
        delete p;
    }

};
int main() {

    std::shared_ptr<CBB>p1(new CBB("xx",20),deletefunc);
    std::shared_ptr<CBB>p2(new CBB("xxxxx",22),deleteclass());

    unique_ptr<CBB, decltype(deletefunc)*>p3(new CBB("ddd",88), deletefunc);
    unique_ptr<CBB, deleteclass>p4(new CBB("ddd",88), deleteclass());
}

weak_ptr

shared_ptr存在的问题

shared ptr 内部维护了一个共享的引用计数器,多个 shared ptr 可以指向同一个资源

如果出现了循环引用的情况,引用计数永远无法归0,资源不会被释放

weak_ptr是什么

weak ptr 是为了配合 shared ptr 而引入的,它指向一个由 shared ptr 管理的资源但不影响资源的生命周期

也就是说,将一个weak ptr 绑定到一个shared ptr 不会改变 shared ptr 的引用计数

不论是否有 weak ptr 指向,如果最后一个指向资源的 shared ptr 被销毁,资源就会被释放

weak ptr 更像是 shared ptr 的助手而不是智能指针

使用

weak_ptr没有重载 -> 和 * 不能直接访问资源

在这里插入图片描述

weak_ptr精髓:

  • weak_ptr 不控制对象的生命周期,但是知道对象是否还活着
  • 用lock成员函数可以提升为shared_ptr
  • 提升的行为lock是线程安全的

weak_ptr和shared_ptr主要用于多线程的问题

文章来源:https://blog.csdn.net/bossDDYY/article/details/135745930
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。