【C++】智能指针

  • ~3.56K 字
  1. 1. 智能指针
    1. 1.1. 普通指针出现的错误
    2. 1.2. 智能指针
    3. 1.3. unique_ptr
      1. 1.3.1. 创建
      2. 1.3.2. 成员函数
      3. 1.3.3. 优点
      4. 1.3.4. 应用
    4. 1.4. shared_ptr
      1. 1.4.1. 创建
      2. 1.4.2. 控制块
      3. 1.4.3. 成员函数
      4. 1.4.4. 类型转换
      5. 1.4.5. 循环引用
    5. 1.5. weak_ptr

智能指针

普通指针出现的错误

  • 内存泄漏:一个指针指向一块动态分配的内存,当该指针离开了所在的作用域,且程序结束后不进行释放,则会导致内存泄漏
  • 悬空指针:指针指向了已经被释放的区域
  • 野指针:指针未初始化使其指向有效内存区域

智能指针

智能指针是一个对象,封装了多态对象指针,可以有效避免上述情况

智能指针会自动申请内存,并在使用结束后释放内存

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是否有效

赞助喵
非常感谢您的喜欢!
赞助喵