智能指针

简介: 从0实现

实现简易智能指针

#include<iostream>
using namespace std;
// 智能指针 保证能做到资源的自动释放
// 智能指针实际利用栈上的对象出作用域自动析构的特征,来做到资源的自动释放
// 因为裸指针是个堆,所以需要手动释放对象,现在写成类后,就可以用栈来自动释放对象了
template<typename T>
class CSmartPtr
{
public:
  CSmartPtr(T *ptr = nullptr)
    :mptr(ptr) {}
  ~CSmartPtr() { delete mptr; }
  T& operator*() { return *mptr; } //必须是T&,否则不能修改mptr指向的对象的值,反而是会创建一个临时副本,把值传给临时副本,而不是原对象
  T* operator->() { return mptr; }
private:
  T *mptr;
};
int main()
{
  CSmartPtr<int> ptrl(new int);
  *ptrl = 20;// 这一行使用了重载的解引用运算符来访问 ptrl 所管理的 int 对象,并将其值设置为 20。
  class Test
  {
  public:
    void test() { cout << "call Test::test" << endl; }
  };
  CSmartPtr<Test> ptr2(new Test());
  // (ptr2->operator->())->test();
  ptr2->test();
  return 0;
}
// 智能指针放在堆上
// CSmartPtr<int> *p = new CSmartPtr<int>(new int); delete p;这时候p是指向堆的智能指针,所以也要手动释放,这样就没啥意义了,还不如直接使用裸指针
// 在这段代码中,p 是一个指向 CSmartPtr<int> 类型的指针。*p 是一个 CSmartPtr<int> 类型的对象。
// 在这里,new int 是一个表达式,它使用 new 运算符动态分配内存来存储一个 int 类型的对象,并返回一个指向该对象的指针。这个指针被传递给 CSmartPtr<int> 的构造函数作为参数

问题

我们继续看下面的代码

CSmartPtr<int> p1(new int);
CSmartPtr<int> p2(p1);

这样的话,是会报错的,因为这会造成一个浅拷贝的问题(等以后再详细解释

那么为了解决浅拷贝的问题,我们用不带引用计数的智能指针和带引用计数的智能指针来解决

不带引用计数的智能指针

不带引用计数的:是只能一个指针管理资源

auto_ptr:C++库里面的

C++11新标准:scoped_ptr和unique_ptr

首先说auto_ptr

auto_ptr<int> p1(new int);
auto_ptr<int> p2(p1);

这样是可以正确运行的

但我们继续

auto_ptr<int> p1(new int);
auto_ptr<int> p2(p1);
*p2 = 20;
cout << *p1 << endl;

这是会报错的,那为什么呢,p1和p2不都是指向同一块内存吗

这时候咱们可以看下auto_ptr的拷贝构造函数看看了

auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {}
_Ty* release() noexcept {
        _Ty* _Tmp = _Myptr;
        _Myptr    = nullptr;
        return _Tmp;
    }

_Right就是咱们传入的p1了,p1调用release后,返回值初始化p2,在源代码里其实就是_Myptr,_Myptr查看源码,它是成员变量,也就是auto_ptr封装的裸指针

private:
    _Ty* _Myptr;

其实auto_ptr在里面是这样走的,首先是把当前指针p1咱们的值先记下来,然后把当前指针p1改成nullptr,最后再把原来的值返回回去给p2

你把上面源代码release中的_Myptr换成p1就好理解了

但这样就带来了一个问题,那就是auto_ptr每次都是只记住最后一个指针,前面的指针都为空了

但我们的本意是想让p1和p2都可以访问到这个地址,所以这样说的话auto_ptr是有些问题的,所以你也能看到一般也都是不推荐使用auto_ptr

经常会被问到能不能再容器当中使用auto_ptr,其实尽量是别搞

vector<auto_ptr<int>> vec1;
vec2(vec1);

因为容器在使用过程中,难免会用到容器的拷贝构造或者容器的赋值,而这样的话,会影响容器内每个元素的拷贝赋值

当你用vec1构造vec2的时候,那就说明vec1里面的指针,全部为空,当你在不知道这个的情况下,你使用vec1里面的智能指针,就全部都是空指针了

既然auto_ptr尽量不用,那scoped_ptr呢

我们先看scoped_ptr的拷贝构造函数和拷贝赋值运算符

scoped_ptr(const scoped_ptr<T>&) = delete;
scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete;

这些语句定义了scoped_ptr的拷贝构造函数和拷贝赋值运算符,它们使用了C++11中的关键字来禁用了这些函数

这意味着你不能使用拷贝构造函数或拷贝赋值运算符来创建一个 scoped_ptr对象的副本,如果你尝试这样做,编译器将报错

其实scoped_ptr的拷贝构造函数是被声明为private并且是没被定义的,这意味着你不能使用拷贝构造函数来创建一个 scoped_ptr对象的副本

这是为了防止多个 scoped_ptr对象管理同一个资源,从而避免在其中一个 scoped_ptr对象销毁时释放资源,导致其他 scoped_ptr 对象悬空

所以 scoped_ptr就不能这样写,这样写就是错的

scoped_ptr<int> p1(new int);
scoped_ptr<int> p2(p1);

那该怎么办呢,也就只剩下unique_ptr了

我们看它的拷贝构造函数和拷贝赋值运算符

unique_ptr(const unique_ptr<T>&) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;

和上面scoped_ptr是一样的,那说明我们这样写也是错的

unique_ptr<int> p1(new int);
unique_ptr<int> p2(p1);

但是如果这样写呢

unique_ptr<int> p1(new int);
unique_ptr<int> p2(std::move(p1));

运行后发现是可以的,那是为什么呢

再看它的源码

发现提供了类似这样的函数

unique_ptr(unique_ptr &&src)
unique_ptr<T>& operator=(unique_ptr<T> &&src)

这是两个右值引用,简单介绍下右值引用吧

右值引用是 C++11 中引入的一种新类型的引用,它绑定到右值(临时对象或将要销毁的对象)上。右值引用通常用于实现移动语义和完美转发。

右值引用使用 && 符号来声明。例如,下面的代码声明了一个 int 类型的右值引用:

int&& rvalue_ref = 5;

在这段代码中,我们将一个右值(字面量 5)绑定到一个右值引用上。

你可以使用 std::move 函数将左值转换为右值引用。例如,下面的代码声明了一个 int 类型的变量,并将其转换为右值引用:

int x = 5;
int&& rvalue_ref = std::move(x);

在这段代码中,我们使用 std::move 函数将左值 x 转换为右值引用,并将其绑定到一个右值引用上。

需要注意的是,使用 std::move 函数并不会移动对象或释放资源。它只是将左值转换为右值引用,以便可以使用移动构造函数或移动赋值运算符来转移对象的所有权。

所以你可以把代码连起来看

unique_ptr(unique_ptr &&src)
unique_ptr<T>& operator=(unique_ptr<T> &&src) 
unique_ptr<int> p1(new int);
unique_ptr<int> p2(std::move(p1));

unique_ptr(unique_ptr &&src)  是unique的移动构造函数,它接收一个右值引用作为参数。当你使用std::move函数将一个unique_ptr对象转化为右值引用并将其传递给另一个unique_ptr对象来初始化时,就会调用这个构造函数

unique_ptr& operator=(unique_ptr &&src) 是unique_ptr的构造赋值运算符,它也接收一个右值引用作为参数。当你使用std::move函数将一个unique_ptr对象转化为右值引用并将其赋值给另一个unique_ptr对象来初始化时,就会调用这个运算符

第三行代码创建一个unique_ptr<int>对象p1,并且使用new int动态分配内存来存储一个int类型的对象

第四行代码创建另一个unique_ptr<int>对象p2,并且使用std::move()将p1转化为右值引用并传递给p2的移动构造函数,这样p1的所有权转移给p2,p1变为空指针

三四行代码,不涉及构造赋值运算符,只涉及到移动构造函数

这时候肯定会说,那和auto_ptr也一样啊,但unique_ptr<int> p2(std::move(p1)); 的优点在于你知道p1是把所有权转移给了p2(毕竟看到了move),但是auto_ptr你可能都没意识到

带引用计数的智能指针

带引用计数:多个指针可以同时管理同一个资源

给每一个对象资源,匹配一个引用计数,当智能指针使用这个资源的时候,引用计数+1,不使用资源的时候,引用计数-1

如果发现这个资源的引用计数为0,说明是最后一个使用这个资源的指针,所以就给它释放掉了

看下面的代码,这份代码实现了所有指针都管理同一份资源,注释我已经写的很详细了,就不继续说了,可以拿过去运行下,一步步跟着走,就知道怎么回事了

#include<iostream>
#include <memory>
using namespace std;
// 对资源进行引用计数的类
template<typename T>
class RefCnt // 重新设置count
{
public:
  RefCnt(T* ptr = nullptr)
    :mptr(ptr)
  {
    if (mptr != nullptr)
    {
      mcount = 1;
    }
  }
  void addRef() { mcount++; } // 增加资源的引用计数
  int delRef() { return --mcount; }
private:
  T* mptr;
  int mcount; // 实际sharedptr里面这个是用atomic_int CAS 原子正项位,是保证了加加减减线程安全
};
// 智能指针 保证能做到资源的自动释放
// 利用栈上的对象出作用域自动析构的特征,来做到资源的自动释放
// 因为裸指针是个堆,所以需要手动释放对象,现在写成类后,就可以用栈来自动释放对象了
template<typename T>
class CSmartPtr // shared_ptr类似,但是我们这里没有考虑多线程因为mcount加加减减的  shared_ptr和weak_ptr都是线程安全,可以直接使用在多线程环境下
{
public:
  CSmartPtr(T *ptr = nullptr)
    :mptr(ptr) 
  {
         // 它创建一个新的RefCnt对象,并使用mptr作为参数来初始化它。然后,它将新创建的RefCnt对象的地址赋值给mpRefCnt成员变量
    mpRefCnt = new RefCnt<T>(mptr);
  }
  ~CSmartPtr() 
  { 
    if (0 == mpRefCnt->delRef())
    {
      delete mptr;
      mptr = nullptr;
    }
  }
  T& operator*() { return *mptr; } //必须是T&,否则不能修改mptr指向的对象的值,反而是会创建一个临时副本,把值传给临时副本,而不是原对象
  T* operator->() { return mptr; }
    // 拷贝构造函数
  CSmartPtr(const CSmartPtr<T>& src)
    :mptr(src.mptr), mpRefCnt(src.mpRefCnt)
  {
    if (mptr != nullptr)
      mpRefCnt->addRef();
  }
  CSmartPtr<T>& operator = (const CSmartPtr<T>& src)
  {
    if (this == &src)
      return *this;
    if (0 == mpRefCnt->delRef())
    {
      delete mptr;
    }
    mptr = src.mptr;
    mpRefCnt = src.mpRefCnt;
    mpRefCnt->addRef();
    return *this;
  }
private:
  T *mptr; // 指向该资源的指针
  RefCnt<T> *mpRefCnt; // 指向该资源引用计数对象的指针
};
int main()
{
  CSmartPtr<int> ptr1(new int);
  CSmartPtr<int> ptr2(ptr1);
  CSmartPtr<int> ptr3;
  ptr3 = ptr2;
  *ptr1 = 20;
  cout << "ptr2:" << *ptr2 << endl;
  cout << "ptr3:" << *ptr3 << endl;
  return 0;
}

简单分析下这个代码

~CSmartPtr() 
  { 
    if (0 == mpRefCnt->delRef())
    {
      delete mptr;
      mptr = nullptr;
    }
  }

这段代码是CSmartPtr类的析构函数。当一个CSmartPtr对象被销毁时,它的析构函数会被调用

在这个析构函数中,首先调用mpRefCnt->delRef()来减少指向的对象的引用计数。然后,检查返回的引用计数是否为零。如果引用计数为零,那么指向的对象不再被任何CSmartPtr对象引用,因此可以安全地删除它。这就是为什么调用delete  mptr;来删除指向的对象

需要注意的是,这段代码并不会调用指向的对象的析构函数。析构函数是在delete mptr;这一行被调用时自动调用的。

当使用new操作符创建一个新的对象时,会为该对象分配内存,并调用其构造函数来初始化它。当不再需要这个对象时,应该使用delete操作符来删除它。这样做会调用该对象的析构函数来清理它所占用的资源,然后释放为它分配的内存

shared_ptr的交叉引用问题

shared_ptr:强智能指针,可以改变资源的引用计数

weak_ptr:弱智能指针,不会改变资源的引用计数

简单说,weak_ptr观察shared_ptr,shared_ptr观察资源(内存),这两个指针之前都是boost库里的,后来C++11采用了他们两个

那为什么咱们非得搞出来个强弱呢,这就是牵扯到强智能指针循环引用(交叉引用)的问题

上面的这份代码,咱们自己写的CSmartPtr也是强智能指针,因为可以改变引用计数

看下面这份代码,use_count()是获得引用计数是多少

#include<iostream>
#include <memory>
using namespace std;
class B;
class A
{
public:
  A() { cout << "A()" << endl; }
  ~A() { cout << "~A()" << endl; }
  shared_ptr<B> _ptrb;
};
class B
{
public:
  B() { cout << "B()" << endl; }
  ~B() { cout << "~B()" << endl; }
  shared_ptr<A> _ptra;
};
int main()
{
  shared_ptr<A> pa(new A());
  shared_ptr<B> pb(new B());
  cout << pa.use_count() << endl;
  cout << pb.use_count() << endl;
  return 0;
}

pa是一个shared_ptr<A>类型的对象。shared_ptr是标准库的一个智能指针类

shared_ptr<A> pa(new A());这行代码,使用一个new A()  创建了一个新的A类型的对象,并将其地址作为参数传递给shared_ptr<A>的构造函数来创建一个新的shared_ptr<A>对象,然后这个新创建的shared_ptr<A>对象被赋值给变量pa

这也就是说变量pa是一个shared_ptr<A>类型的对象,它指向一个新创建的A类型的对象,并管理这个对象的生命周期

输出结果为

A()
B()
1
1
~B()
~A()

这时候再把代码改成如下

shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->_ptrb = pb;
pb->_ptra = pa;
cout << pa.use_count() << endl;
cout << pb.use_count() << endl;

输出为

A()
B()
2
2

发现都是2,就算-1的话,也都是1,根本不能达到为0的状态,所以无法去析构函数,这就是循环引用了

现在回答强智能指针循环引用(交叉引用)是什么问题?什么结果?怎么解决?

造成的结果是:造成new出来的资源无法释放,资源泄漏问题!

看下面的图:

shared_ptr<A> pa(new A())

shared_ptr<B> pa(new B())

这两个都在栈上

new A() 和 new B()

这两个都在堆上

所以最后程序结束时的时候,先减shared_ptr<B> pa(new  B());,发现堆内存引用从2到1,再减shared_ptr<A> pa(new  A());发现也是从2到1,右边这两个堆内存,只有两个互相指,其他人不知道了,所以就丢了

在上面提到的代码中,由于存在循环引用,pa和pb指向的对象的析构函数不会被调用

因为当pa和pb离开作用域并被销毁时,它们的析构函数会被调用。然而,由于它们指向的对象仍然被对方所引用,因此这些对象的引用计数不会变为零。这意味着,这些对象不会被删除,它们的析构函数也不会被调用

记住只有当引用计数变为0,指向的对象才会被删除,才会调用析构函数

上面这些话可能有点绕,结合我上面对这个代码的分析,可以理解下

~CSmartPtr() 
{ 
  if (0 == mpRefCnt->delRef())
  {
    delete mptr;
    mptr = nullptr;
  }
}

那该怎么解决循环引用的问题呢?

定义对象的时候,用强智能指针;引用对象的时候,使用弱智能指针

把上面代码改成这样的

// 这是定义对象
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
// 这是引用对象
class B;
class A
{
public:
  A() { cout << "A()" << endl; }
  ~A() { cout << "~A()" << endl; }
  weak_ptr<B> _ptrb;
};
class B
{
public:
  B() { cout << "B()" << endl; }
  ~B() { cout << "~B()" << endl; }
  weak_ptr<A> _ptra;
};

输出结果是

A()
B()
1
1
~B()
~A()

弱智能指针,只会观察资源,不能改变和使用资源,根本就没有提供运算符重载operator*,重载函数operator-> ,它不能够访问资源和裸指针是不一样的

例如

class B;
class A
{
public:
  A() { cout << "A()" << endl; }
  ~A() { cout << "~A()" << endl; }
  void testA() { cout<< "非常好用的方法!" << endl; }
  weak_ptr<B> _ptrb;
};
class B
{
public:
  B() { cout << "B()" << endl; }
  ~B() { cout << "~B()" << endl; }
  void func()
  {
    _ptra->testA(); //这是错误的,因为只会观察,不能使用资源
  }
  weak_ptr<A> _ptra;
};

如果想使用资源则需要这样改

void func()
  {
    // _ptra->testA(); 这是错误的,因为只会观察,不能使用资源
    shared_ptr<A> ps = _ptra.lock(); // 提升方法,把弱转为强
    // 但是得判断下是否提升成功,因为提升过程中,可能资源已经被释放了
    if(ps != nullptr)
    {
      ps->testA();
    }
  }

多线程访问共享对象的线程安全问题

接下来讲讲强弱智能指针的应用

比如C++著名开源网络库muduo库

这涉及到多线程访问共享对象的线程安全问题

首先看这个代码

#include<iostream>
#include <memory>
#include <thread>
using namespace std;
class A
{
public:
  A() { cout << "A()" << endl; }
  ~A() { cout << "~A()" << endl; }
  void testA() { cout << "非常好用的方法!" << endl; }
};
// 子线程
void headler01(A* q)
{
  q->testA();
}
// main线程
int main()
{
  A* p = new A();
  thread t1(headler01, p);
  // 先让主线程睡上两秒,再去delete p
  std::this_thread::sleep_for(std::chrono::seconds(2));
  delete p;
  t1.join();
  return 0;
}

输出为

A()
非常好用的方法!
~A()

把代码改编为如下

// 子线程
void handler01(A* q)
{
  std::this_thread::sleep_for(std::chrono::seconds(2));
  q->testA();
}
// main线程
int main()
{
  A* p = new A();
  thread t1(handler01, p);
  delete p;
  t1.join();
  return 0;
}

意思是主线程拉起子线程后,立马释放这个对象指针p,那这时候再去q->testA();还可以吗

实际输出却是

A()
~A()
非常好用的方法!

事实上,当你在主线程中使用delete删除对象p时,它会释放该对象所占用的内存。但是,这并不意味着该内存立即被操作系统回收或被其他程序使用。因此,即使对象p被删除,子线程仍然可以访问它所在的内存地址并调用它的方法

但是,这样的行为是不安全的,因为在删除对象后访问它会导致未定义行为。在这种情况下,程序可能会崩溃或产生意外的结果。建议使用智能指针来管理对象的生命周期,以避免这种问题

所以咱们得再调用testA的时候,看看A对象是否还活着,这时候就用到了强弱指针

那就在handler传入一个weak_ptr pw,如果A对象不存在,那就不用管了,但是A对象如果存在的话,那就提升pw为强指针,因为它就是指向对象A的

void handler01(weak_ptr<A> pw)
{
  std::this_thread::sleep_for(std::chrono::seconds(2));
  // pw访问A对象的时候,需要侦测以下A对象是否存活
  shared_ptr<A> sp = pw.lock();
  if (sp != nullptr)
  {
    sp->testA();
  }
  else
  {
    cout << "A对象已经被析构,不能再访问!" << endl;
  }
}
// main线程
int main()
{
  //这个大括号是作用域
  {
    shared_ptr<A> p(new A());
    thread t1(handler01, weak_ptr<A>(p));
    t1.detach();
  }
  std::this_thread::sleep_for(std::chrono::seconds(20));
  return 0;
}

输出为

A()
~A()
A对象已经被析构,不能再访问!

再改代码

void handler01(weak_ptr<A> pw)
{
  // pw访问A对象的时候,需要侦测以下A对象是否存活
  shared_ptr<A> sp = pw.lock();
  if (sp != nullptr)
  {
    sp->testA();
  }
  else
  {
    cout << "A对象已经被析构,不能再访问!" << endl;
  }
}
// main线程
int main()
{
  //这个大括号是作用域
  {
    shared_ptr<A> p(new A());
    thread t1(handler01, weak_ptr<A>(p));
    t1.detach();
    //让主线程等2s
    std::this_thread::sleep_for(std::chrono::seconds(2));
  }
  std::this_thread::sleep_for(std::chrono::seconds(20));
  return 0;
}

输出

A()
非常好用的方法!
~A()

现在讲讲t1.detach为什么在这里有,而t1.join()在这里没有呢

在这段代码中,t1.detach() 用于将子线程 t1 与主线程分离。这意味着主线程不再等待子线程 t1 完成,而是继续执行后面的代码。这样,主线程就可以在子线程 t1 运行时继续执行其他任务

由于子线程 t1 已经与主线程分离,所以不能再使用 t1.join() 来等待子线程完成。如果在分离后调用 t1.join(),程序会抛出异常

使用 detach() 方法可以让子线程独立运行,但是也会带来一些风险。例如,如果主线程结束时子线程仍然在运行,那么子线程可能会被强制终止,导致资源泄漏或其他问题。因此,在使用 detach() 方法时应谨慎

自定义删除器

智能指针:能够保证资源的绝对释放,里面默认都是delete ptr释放资源的

但不是所有的资源都是能够通过delete释放的,毕竟资源那么多种类

比如我用智能指针托管数组的话,那delete就不行,得用delete[]

再比如我让它管理的不是内存资源,而是文件资源,那释放文件也绝对不可能用delete释放的

所以在我们除了智能指针在堆内存外,怎么正确指导智能指针来正确删除呢

先讲讲智能指针内部是咋回事吧

unique_ptr shared_ptr 一个不带计数,一个带计数

他们两个都是可以带自定义删除器的

看他们的源码

~unique_ptr(){ 是一个函数对象的调用 deletor(ptr) }相当于deletor调用了他的小括号运算符重载函数

默认的deletor是这样的,C++里面定义的是

template<typename T> class default_delete //就是上面说的deletor { public: void operator() (T *ptr) { delete ptr; } }; 如果我们想自定义删除的话,我们给它提供一个类似这样的就行了

default_delete看源码就知道它在第二个参数中

_EXPORT_STD template <class _Ty, class _Dx /* = default_delete<_Ty> */>

所以我们自定义删除的话,也是在第二个上面改

template<typename T>
class MyDeletor
{
public:
  // ()重载运算符
  void operator()(T* ptr)const
  {
    cout << "class MyDeletor.operator()" << endl;
    delete[] ptr;
  }
};
int main()
{
  unique_ptr<int, MyDeletor<int>> ptr1(new int[100]); //delete[] 所以不能用deletor,因为里面还是delete
  return 0;
}

当智能指针出作用域的时候,栈要回收的时候,他这时候智能指针调用的deletor,现在这个类型实际就是我们的MyDeletor<int>这个类型,因为看源码会知道unique_ptr的第二个参数就是删除用的,所以deletor(ptr)小括号运算符重载函数也是调用的它MyDeletor<int>的小括号运算符重载函数

再比如文件类型的话

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include <memory>
#include <thread>
#include <fstream>
using namespace std;
template<typename T>
class MyFileDeletor
{
public:
  // ()重载运算符
  void operator()(T* ptr)const
  {
    cout << "class MyFileDeletor.operator()" << endl;
    fclose(ptr);
  }
};
int main()
{
  unique_ptr<FILE, MyFileDeletor<FILE>> ptr2(fopen("data.txt", "w"));
  return 0;
}

但发现这种方式很繁琐,我们写类只是为了在删除中用到,能不能简单一些呢

比如数据就数组Deletor,文件是文件Deletor太麻烦了

想到用lambda表达式,替代函数对象

但问题来了,我们只能从lambda知道函数对象,但是MyFileDeletor<FILE>和MyDeletor<int>都是函数对象类型

这时候lambda提供的有function可以解决这个问题

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include <memory>
#include <thread>
#include <fstream>
#include <functional>
using namespace std;
template<typename T>
class MyDeletor
{
public:
  // ()重载运算符
  void operator()(T* ptr)const
  {
    cout << "class MyDeletor.operator()" << endl;
    delete[] ptr;
  }
};
template<typename T>
class MyFileDeletor
{
public:
  // ()重载运算符
  void operator()(T* ptr)const
  {
    cout << "class MyFileDeletor.operator()" << endl;
    fclose(ptr);
  }
};
int main()
{
  // unique_ptr<int, MyDeletor<int>> ptr1(new int[100]);
  unique_ptr<int, function<void(int*)>>ptr1(new int[100],
    [](int* p)->void {
      cout << "call lambda release new int[100]" << endl;
      delete[] p;
    }
  );
  // unique_ptr<FILE, MyFileDeletor<FILE>> ptr2(fopen("data.txt", "w"));
  unique_ptr<FILE, function<void(FILE*)>> ptr2(fopen("data,txt", "w"),
    [](FILE* p)->void {
      cout << "call lambda release new fopen" << endl;
    }
  );
  return 0;
}

最后输出是

call lambda release new fopen
call lambda release new int[100]

这段代码中的 unique_ptr 使用了两个模板参数。第一个参数是指针所指向的类型,即 int。第二个参数是删除器类型,即 function<void(int*)>。删除器是一个函数对象,用于在智能指针销毁时释放其所指向的资源

在这段代码中,删除器是一个 lambda 表达式。lambda 表达式是一种匿名函数,它可以捕获上下文中的变量并在其函数体中使用。这个 lambda 表达式接受一个 int 指针作为参数,并在其函数体中使用 delete[] 运算符来释放该指针所指向的数组

ptr1 被销毁时,它会调用这个 lambda 表达式来释放其所指向的数组,并输出一条消息表示已经调用了删除器

相关文章
|
1月前
|
安全 程序员 编译器
C++中的RAII(资源获取即初始化)与智能指针
C++中的RAII(资源获取即初始化)与智能指针
20 0
|
1月前
|
安全 程序员 C++
C++中的智能指针:从原始指针到现代内存管理
C++中的智能指针:从原始指针到现代内存管理
18 0
|
3月前
|
C++
C++:一文读懂智能指针
C++:一文读懂智能指针
45 0
|
3月前
|
消息中间件 Kubernetes NoSQL
智能指针的种类以及使用场景
智能指针的种类以及使用场景
|
3月前
|
安全 C++
c++11新特性——智能指针详解
c++11新特性——智能指针详解
|
1月前
|
安全 C++ 容器
C++中的智能指针:自动内存管理的利器
C++中的智能指针:自动内存管理的利器
23 0
|
3月前
|
设计模式 Rust Java
【一起学Rust | 设计模式】习惯语法——默认特质、集合智能指针、析构函数
【一起学Rust | 设计模式】习惯语法——默认特质、集合智能指针、析构函数
56 0
|
1月前
|
存储 Rust C++
C++智能指针
【2月更文挑战第14天】介绍C++智能指针
22 0
|
2月前
|
算法 安全 C++
C++ Effective Modern Pointer (智能指针模块)
C++ Effective Modern Pointer (智能指针模块)