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