智能指针
普通指针出现的错误
- 内存泄漏:一个指针指向一块动态分配的内存,当该指针离开了所在的作用域,且程序结束后不进行释放,则会导致内存泄漏
- 悬空指针:指针指向了已经被释放的区域
- 野指针:指针未初始化使其指向有效内存区域
智能指针
智能指针是一个对象,封装了多态对象指针,可以有效避免上述情况
智能指针会自动申请内存,并在使用结束后释放内存
C++中存在封装了三种智能指针
- unique_ptr
- shared_ptr
- weak+ptr
unique_ptr
unique_ptr与管理对象是一对一的关系
创建
unique_ptr(A) ptr1(new A(参数))
unique_ptr(A) ptr1 =make_unique<A>(参数)(该方法较为安全)
其中A为类名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<memory>
#include<iostream>
using namespace std;
class rectangle{
public:
rectangle(double w, double h): width(w), height(h){}
~rectangle(){
cout <<"对象被释放"<<endl;
}
double area(){
return width*height;
}
private:
double width;
double height;
};
int main(){
unique_ptr<rectangle> p(new rectangle(3.5, 4.1));
cout << p->area()<<endl;
}1
2
14.35
对象被释放这里智能指针可以像普通指针一样使用,直接调用成员
离开作用域后,会自动销毁,调用析构函数释放指向的对象
注意:unique_ptr不能使用拷贝构造,也不能被赋值
成员函数
T* get();:获得所管理对象的指针T* operator->();:重载间接成员运算符,调用get()函数,返回所管理对象指针T& operator*();:重载解引用运算符,返回所管理对象的引用T* release();:解除对封装对象的管理,返回对象指针void reset(T* newObj):删除原有对象,接管新对象void swap(unique_ptr<T>& other):与其他unique_ptr对象交换所管理的对象move():转移指针所管理对象的所有权(转移后先前的指针就是野指针了)
优点
安全,且开销(时间与空间)与普通指针差距不大,成员函数带来的额外开销也不大,符合RAII
应用
适用于使用普通指针的地方,如容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Packet{
Packet(long id):m_id(id){};
long m_id;
char Data[1000];
};
struct Compare{
template<template<typename> typename SmartPtr>
bool operator()(const SmartPtr<Packet>& pA, const SmartPtr<Packet>& pB){
return pA->m_id<pB->m_id;
}
};
template<typename SmartPtr>
void sortSmartPtrVector(int n){
vector<SmartPtr> vecPacket;
for(int i=0;i<n; i++){
vecPacket.push_back(SmartPtr(new Packet(rand()%n)));
}
sort(vecPacket.begin(), vecPacket.end(),Compare());
}shared_ptr
多个shared_ptr通过一个共同的引用计数器来管理同一个对象
创建
shared_ptr(A) ptr1(new A(参数))
shared_ptr(A) ptr1 =make_shared<A>(参数)(该方法较为安全)
其中A为类名
控制块
shared_ptr需要分配另一块内存用来存储引用计数
当引用计数为0,即所有指向当前引用的指针都消失后,当前类才会被删除
成员函数
shared_ptr没有release方法
long use_count():获取有多少指针管理同一个对象bool unique():返回use_count()是否为1
类型转换
dynamic_pointer_cast<>()static_pointer_cast<>()const_pointer_cast<>()
循环引用
使用shared_ptr时可能会出现循环引用的情况,如在使用容器时
此时的引用计数会变为2,但当某个指针离开时只会见一,导致无法自动删除对象导致内存泄漏
我们可以使用weak_ptr来避免这种情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <memory>
using namespace std;
class c{
public:
c(){
cout <<"构造函数被调用"<<endl;
}
~c(){
cout <<"析构函数被调用"<<endl;
}
};
int main(){
{
shared_ptr<c> spo;
{
cout<<"进入作用域"<<endl;
shared_ptr<c> spi = make_shared<c>();
spo = spi;
}
cout<<"离开作用域"<<endl;
}
}weak_ptr
weak_ptr需要结合shared_ptr来使用
可以将一个shared_ptr对象作为构造函数参数初始化,或直接赋值
weak_ptr只对shared_ptr管理的对象进行观测,不改变对象的引用计数
可以通过对weak_ptr使用lock()函数,来获得一个shared_ptr以获得封装对象的控制权
若该shared_ptr非空,说明可用,且增加一次引用计数,在离开作用域前可以保证所管理对象是有效的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<memory>
#include<iostream>
using namespace std;
class rectangle{};
int main(){
weak_ptr<rectangle> wp1;
{
shared_ptr<rectangle> sp1(new rectangle());
shared_ptr<rectangle> sp2 = sp1;
wp1 = sp2;
shared_ptr<rectangle> sp3 = wp1.lock();
cout <<"作用域内sp3="<<sp3<<endl;
}
shared_ptr<rectangle> sp3 = wp1.lock();
cout <<"作用域外sp3="<<sp3<<endl;
}离开内部作用域后,对象的引用计数变为0,此时被析构,故wp1指向了空位置,可以判断对象有效性
使用expired()方法可以询问一个weak_ptr是否有效