C++智能指针详解

 更新时间:2022年08月17日 17:13:20   作者:顽張先生  
从比较简单的层面来看,智能指针是RAII(Resource Acquisition Is Initialization,资源获取即初始化)机制对普通指针进行的一层封装。这样使得智能指针的行为动作像一个指针,本质上却是一个对象,这样可以方便管理一个对象的生命周期

优缺点:

  • 智能指针由原始指针的封装,优点是可以自动分配内存,不用担心内存泄漏问题。
  • 用于解决独占/共享所有权指针的释放,传输等问题。
  • 但是没有原始指针方便。

一. unique_ptr独占指针

特点

都是围绕独占展开

特点一: 如其名,独占。也就是说同一个内存空间同时只能有一个指针来管理。

int* pi = new int(10); //利用传统指针在堆区开辟一个存放整数的区域
std::unique_ptr<int> u_pi_01{pi};//通过传统指针创建智能指针
std::unique_ptr<int> u_pi_02{pi};//有意让两个独占指针同时指向同一个内存区域

这么写编译器不会报错,但运行时会提示 error:double free detected in tcache 2

这也就印证了第一个特点,一个内存区域只能由一个指针管理。

特点二: 当指针超出作用域时,内存自动释放

//由于指针的本质也是变量,离开作用范围就会自动释放
//因此我们需要通过在外部创建变量来保存指针所保存的地址
int* external_pi_1;//用于存储传统指针的地址
int* external_pi_2;//用于存储智能指针的地址
 {
    int *pi = new int(10);//使用传统指针在堆区开辟内存存储整形
    external_pi_1 = pi;   
    std::unique_ptr<int> u_pi{new int(10)};//使用智能指针在堆区开辟内存存储
    external_pi_2 = u_pi.get();
    std::cout << *external_pi_1 << std::endl;//输出10
    std::cout << *external_pi_2 << std::endl;//输出10
 }
std::cout << *external_pi_1 << std::endl;//输出10
std::cout << *external_pi_2 << std::endl;//输出0

可见传统指针在局部作用域中开辟的内存在外部同样可以访问,也就是说我们使用传统指针开辟内存之后在离开作用域时需要加上释放内存的操作,不然会造成内存泄漏。

而智能指针我们不需要手动释放内存,在离开作用域后会自动释放。

特点三:由于特点一,修改指针不可以copy ,只能Move(转移归属权)

std::unique_ptr<int> u_pi1 = std::make_unique<int>(10);
//std::unique_ptr<int> u_pi2 = u_pi1;//尝试用拷贝的方式共享内存,error:可别忘了这是独占指针
std::unique_ptr<int> u_pi2 = move(u_pi1);使用move方法转移内存拥有权。

也就是说,通过move函数,把指针u_pi1所指内存中的值掏空,然后安到指针u_pi2所指的内存上。

创建方式

方式一: 通过已有的传统指针创建

int* pi = new int(10); //使用传统指针在堆区开辟一个空间
std::unique_ptr<int> u_pi{pi};//利用创通指针创建智能指针

方式二: 通过new方法创建

std::unique_ptr<int> u_pi{new int(10)};

方式三: 通过std :: make_unique创建

std::unipue_ptr<int> u_pi = std::make_unique<int>(10);

传递方式

方式一: 通过move(),转移拥有权.

void show(std::unique_ptr<int> u_pi)
{
    std::cout<<*u_pi<<std::endl;
}
void test()
{
    std::unique_ptr<int> u_pi{new int(10)};
    show(move(u_pi)); //通过move转移拥有权
}

注意:将指针的拥有权转入函数中后,在原作用域指针将被释放,而该指针将在函数调用结束时释放。也就是说,将智能指针以move的形式传入函数后,在原作用域不能再使用该指针。

方式二: 通过引用传递

void show(const std::unique_ptr<int> &u_pi)//加cosnt 不是不能改变指向的值,不能改变指针的指向
{
    std::cout << *u_pi << std::endl;
    //u_pi.reset();加了const所以不能清空
}
void test()
{
    std::unique_ptr<int> u_pi{new int(10)};
    show(u_pi);
}

注意: 将指针以引用的方式传入函数,那么该指针在原作用域依然存活,并可以和所调用函数共同操作该内存空间数据。

方式三: 链式传递

std::unique_ptr<Person> get_unique(std::string str)
{
    std::unique_ptr<Person> u_pi{new Person(str)};
    return u_pi;
}
void test()
{
    get_unique("hua")->show();//链式
}

简单使用

  • 通过get()获取地址
  • 可以通过->调用成员函数
  • 可以通过*调用解引用
  • 通过reset()清空指针
class Person
{
public:
    Perosn(std::string name):m_name(name){};
    void show()
    {
        std::cout<<"name is "<<m_name<<std::endl;
    }
private:
    std::string m_name;   
};
int main()
{
std::unique_ptr<Person> u_p{new Person("kimi")}; //用自定义类型创建
u_p->show();//可以通过->调用函数
(*u_p).show();//通过*解引用
std::cout<<u_p.get()<<std::endl;//通过get()获取地址
u_p.reset();//清空指针
return 0;
}

隐藏危险

用已有指针创建时,没有及时清空传统指针,导致同时有两个指针指向这块已经被“独占”的区域。

int* pi = new int(10);
std::unique_ptr<int> u_pi{pi};//使用传统指针创建,上式开辟的区域被独占
*pi = 20; //没有及时清空,依然可以通过独占指针以外的方式修改内存

二. shared_ptr 计数指针

特点

特点一: 可以通过copy共享内存。

std::shared_ptr<int> u_pi_1{new int(10)};
std::shared_ptr<int> u_pi_2 = u_pi_1;//通过复制拷贝

特点二: 通过use_count();来查看计数 ,copy 计数加一,销毁计数减一。

std::shanred_ptr<int> s_pi{new int(10)}; //s_pi.use_count() == 1
std::shanred_ptr<int> s_pi_copy = s_pi;  //s_pi.use_count() == 2
s_pi = nullptr;//清空指针                 //s_pi_copy.use_count() == 1

特点三: 无论多少指针,都同用一份数据,因而同一份数据的use_count()一致。

std::shared_ptr<int> s_pi{new int(10)}; //s_pi.use_count() == 1
std::shared_ptr<int> s_pi_2 = s_pi;     //s_pi.use_count() == 2
std::shared_ptr<int> s_pi_3 = s_pi_2;   //s_pi.use_count() == 3
s_pi_2 = bullptr; //清空2指针            //s_pi.use_count() == 2

传递方式

  • 本质不变,在函数调用中,因为本身支持复制操作,所以不用加move可以直接传递。
  • 并且在传递到函数中,use_count() 会增加,并在函数销毁时候还原。
  • 在函数中修改指向的值,在外部的指针指向的值也会改变。
  • 使用引用传递,则在传递到函数中时,计数不会增加。
void get_use_1(std::shared_ptr<int> s_pi)
{
    std::cout << s_pi.use_count() << std::endl;
}
void get_use_2(std::shared_ptr<int>& s_pi)
{
    std::cout << s_pi.use_count() << std::endl;
}
void test()
{
    std::shared_ptr<int> s_pi{new int(10)};
    std::cout << s_pi.use_count() << std::endl;
    get_use_1(s_pi);//在函数中计数会增加,但随着函数销毁,计数复原
    get_use_2(s_pi);//以引用方式传入,指针还是那个指针,计数不会增加
}

输出:1 2 1

隐藏危险

share_ptr带来的循环依赖问题

class Person
{
public:
    void set_friend(share_ptr<Person> p)
 
    _friend = p;
private:
    share_ptr<Person> _frient;
};
int main()
{
   share_ptr<Person> p1 = make_shared("P1");
   share_ptr<Person> p2 = make_shared("P2");
    p1->set_friend(P2);
    p2->set_friend(P1);//造成循环依赖,在main中的话,不会执行析构
}

解决:将_friend属性改为weak_ptr 。

三. weak_ptr

weak_ptr 是一个不需要所有权的指针,所以我们可以通过用weak_ptr来声明属性,解决循环依赖

class Person
{
public:
    void set_friend(share_ptr<Person> p)
    _friend = p;
private:
    weak_ptr<Person> _frient;//使用weak_ptr解决循环依赖
};
int main()
{
    share_ptr<Person> p1 = make_shared("P1");
    share_ptr<Person> p2 = make_shared("P2");
    p1->set_friend(P2);
    p2->set_friend(P1);
}

可以通过lock()来将weak_pte升级为shared_ptr;

std::weak_ptr<Person> w_pi{new Person("hua")};
std::shared_ptr <Person> s_pi2 = w_pi.lock();

到此这篇关于C++智能指针详解的文章就介绍到这了,更多相关C++智能指针内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 一文详解Qt的QObject类

    一文详解Qt的QObject类

    Qt的QObject类是Qt框架中的基类,它是所有Qt对象的父类,本文主要介绍了Qt的QObject类,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • 详解C++编程中的vector类容器用法

    详解C++编程中的vector类容器用法

    vector是一个标准库中的容器,使用时需要包含#include <vector>头文件,也可以说vector是一个类模板而不是一种数据类型,对它的定义,需要指定类型,需要的朋友可以参考下
    2016-05-05
  • 在C++中加载TorchScript模型的方法

    在C++中加载TorchScript模型的方法

    这篇文章主要介绍了在C++中加载TorchScript模型的方法,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-02-02
  • wxWidgets实现无标题栏窗口拖动效果

    wxWidgets实现无标题栏窗口拖动效果

    这篇文章主要为大家详细介绍了wxWidgets实现无标题栏窗口拖动效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-02-02
  • 一文带你了解C++中deque的使用

    一文带你了解C++中deque的使用

    C++中的deque是一种双端队列,可以在队列的前端和后端进行插入元素和删除操作,同时可以视作一个长度不定的数组,支持高效的插入和删除操作。本篇文章将深入探讨C++中的deque的使用,感兴趣的可以了解一下
    2023-05-05
  • c语言中联合体和枚举用法详解

    c语言中联合体和枚举用法详解

    结构体、联合体是C语言中的构造类型,结构体我们平时应该都用得很多,下面这篇文章主要给大家介绍了关于c语言中联合体和枚举用法的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-12-12
  • C语言 指针与二维数组详解

    C语言 指针与二维数组详解

    本文主要介绍C语言 指针与二维数组,这里整理了详细的资料及示例代码,有需要的小伙伴可以参考下
    2016-08-08
  • C语言的指针类型详细解析

    C语言的指针类型详细解析

    C语言的指针类型包括两方面的信息:一是地址,存放在指针变量中;二是类型信息,关乎于读写的长度,没有存储在指针变量中,位于用该指针读写时的mov指令中,不同的读写长度对应的mov指令不同
    2013-09-09
  • MFC自绘Button按钮分析和实现

    MFC自绘Button按钮分析和实现

    这篇文章主要为大家详细介绍了MFC自绘Button按钮分析和实现,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-02-02
  • C/C++ 中实现让控制台暂停的方法

    C/C++ 中实现让控制台暂停的方法

    这篇文章主要介绍了C/C++ 中实现让控制台暂停的方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07

最新评论