C++智能指针

1. 智能指针的目的
智能指针是一个模板类,设计目的是因为在C++程序设计中,需要频繁使用堆内存,管理指针非常麻烦,又很容易出现忘记释放内存造成内存泄漏或者二次释放、程序发生异常时内存泄露等问题。使用智能指针可以更好的管理内存。

2.智能指针的原理
智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。
智能指针类的构造函数中传入一个普通指针,析构函数中释放传入的指针。智能指针的类都是栈上的对象,所以当函数(或程序)结束时会自动被释放。

3 常见智能指针
c++98中有智能指针auto_ptr
auto_ptr智能指针不支持复制和赋值(操作符重载),已被c++11弃用,因为它会造成一些问题
可能存在内存泄露的风险

#include <iostream>
#include <memory>
using namespace std;
int main()
{
    auto_ptr<int> a(new int (1));
    auto_ptr<int> b;
    b=a; 严重错误,不会报错,但是b剥夺了a的所有权,a不在指向有效数据了,这时再调用a就会出现内存泄漏
    cout<<*a<<endl;
}

智能指针在C++11版本之后提供,包含在头文件中,shared_ptr、unique_ptr、weak_ptr
unique_ptr

unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。把一个unique_ptr赋值给另一个的时候会报错,但是如果把临时右值赋给unique_ptr的时候不会报错。

#include <iostream>
#include <memory>
using namespace std;
int main()
{
    unique_ptr<int> a(new int (1));
    unique_ptr<int> b;
    b=a; 错误,不能赋值
    cout<<*a<<endl;
}
#include <iostream>
#include <memory>
using namespace std;
int main()
{
    //unique_ptr<int> a(new int (1));
    unique_ptr<int> a;
    a=unique_ptr<int>(new int (1)); ///临时右值可以赋值
    cout<<*a<<endl;
}

shared_ptr
shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。

  • 初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针,一个是类,一个是指针。例如std::shared_ptr p4 = new int(1);的写法是错误的
  • 拷贝和赋值。拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。后来指向的对象引用计数加1,指向后来的对象。
  • get函数获取原始指针
  • 注意不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存
  • 注意避免循环引用,shared_ptr的一个最大的陷阱是循环引用,循环,循环引用会导致堆内存无法正确释放,导致内存泄漏。循环引用在weak_ptr中介绍。
  • 线程安全问题:
    shared_ptr 本身不是 100% 线程安全的。它的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化。shared_ptr 的线程安全级别和内建类型、标准库容器、string 一样,即:
    一个 shared_ptr 实体可被多个线程同时读取;
    两个的 shared_ptr 实体可以被两个线程同时写入,“析构”算写操作;
    如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
    int a=1;
    shared_ptr<int> ptra=make_shared<int>(a);
    shared_ptr<int> ptrb(ptra);
    cout<<ptra.use_count()<<endl; ///输出2,因为使用构造函数一次,复制构造函数一次,一共两次
    shared_ptr<int> ptrc=ptra;    ///把a赋给c,相当于又多了一个c指向相同的内存,现在,a,b,c都指向相同的内存
    cout<<ptra.use_count()<<endl; ///输出3

    int b=2;
    int *p=&b;
    shared_ptr<int> ptrd(p);
    ptrc=ptrd;
    cout<<ptra.use_count()<<endl; ///原来引用次数是3,其中ptrc让别的给赋值了,所以ptra的次数减一
    cout<<ptrd.use_count()<<endl; ///ptrd引用次数由于赋值给了ptrc一次,所以加一,变成了2


}

shared_ptr循环引用造成内存泄漏的问题:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include"boost/shared_ptr.hpp"
 
 
struct Node
{
    Node(int value)
    :_value(value)
    {
        cout << "Node()" << endl;
    }
    ~Node()
    {
        cout << "~Node()" << endl;
    }
    shared_ptr<Node> _prev;
    shared_ptr<Node> _next;
    int _value;
};
 
void Test2()
{
    shared_ptr<Node> sp1(new Node(1));
    shared_ptr<Node> sp2(new Node(2));
    cout << sp1.use_count() << endl;
    cout << sp2.use_count() << endl;
    sp1->_next = sp2;
    sp2->_prev = sp1;
    cout << sp1.use_count() << endl;
    cout << sp2.use_count() << endl;
}
 
int main()
{
    Test2();
    return 0;
}

在这里插入图片描述
由于先构造的后释放,后构造的先释放可知,先释放的是sp2,那么因为它的引用计数为2,减去1之后就成为了1,不能释放空间,因为还有其他的对象在管理这块空间。但是sp2这个变量已经被销毁,因为它是栈上的变量,但是sp2管理的堆上的空间并没有释放。
接下来释放sp1,同样,先检查引用计数,由于sp1的引用计数也是2,所以减1后成为1,也不会释放sp1管理的动态空间。

通俗点讲:就是sp2要释放,那么必须等p1释放了,而sp1要释放,必须等sp2释放,所以,最终,它们两个都没有释放空间。最终导致了内存泄漏。(p2死,必须要求p1先死,p1死,也要求p2先死,他俩互相尬)。

weak_ptr

  • 弱引用可以帮助解决上述shared_ptr强引用的循环引用问题;
    原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。
  • (弱引用当引用的对象活的时候不一定存在 。仅仅是当它存在的时候的一个引用。弱引用并不修改该对象的引用技术,这意味这弱引用它并不对对象的内存进行管理,在功能上类似普通的指针,然而一个比较大的区别是:弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存)
  • weak_ptr 必须从一个share_ptr或者另一个weak_ptr转换而来,不能使用new 对象进行构造。这也说明,进行该对象的内存管理的是那个强引用的shared_ptr。weak_ptr只是提供了对管理对象一个访问手段。
struct ListNode
{
    int _data;
    weak_ptr<ListNode> _prev;  ///弱引用
    weak_ptr<ListNode> _next;
    ~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
    shared_ptr<ListNode> node1(new ListNode);
    shared_ptr<ListNode> node2(new ListNode);
    cout << node1.use_count() << endl;
    cout << node2.use_count() << endl;
    node1->_next = node2;  ///在这一块不更改引用计数
    node2->_prev = node1;///在这一块不更改引用计数
    cout << node1.use_count() << endl;
    cout << node2.use_count() << endl;
    return 0; 
}

参考博客https://blog.csdn.net/qq_34992845/article/details/69218843

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值