红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【C++】智能指针

智能指针

普通指针出现的错误

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

智能指针

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

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

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