C++11之智能指针
为什么需要智能指针
#include <iostream> using namespace std; int div() { int a, b; cin >> a >> b; if (b == 0) throw invalid_argument("除0错误"); return a / b; } void Func() { // 1、如果p1这里new 抛异常会如何? // 2、如果div调用这里又会抛异常会如何? int* p1 = new int[10]; cout << div() << endl;//如果div抛出异常那么就会导致内存泄漏! //因为后面的delete无法执行!会直接跳出这个函数 delete[] p1; } int main() { try { Func(); } catch (const exception& e) { cout << e.what() << endl; } return 0 }
解决办法 一
void Func() { int* p1 = new int[10]; try { cout << div() << endl; } catch (...) { delete[]p1; throw; } delete[]p1; }
==但是这样子虽然解决了,但是首先这个代码不整洁和美观,其次,这个代码还有一个问题==
void Func() { int* p1 = new int[10]; int* p2 = new int[10]; try { cout << div() << endl; } catch (...) { delete[]p1; delete[]p2; throw; } delete[]p1; delete[]p2; }
这个为什么有问题?——因为new本身也有可能会抛出异常!如果p1抛出异常还好!==但是如果抛出异常的是p2呢?——那么p1内存就无法被释放导致内存泄漏!因为p2抛出异常是直接回到main函数的!既不会执行下面的delete代码,也不会进入下面的catch!==
void Func() { // 1、如果p1这里new 抛异常会如何? // 3、如果div调用这里又会抛异常会如何? int* p1 = new int[10]; int* p2 = nullptr; try { p2 = new int[10]; try { cout << div() << endl; } catch (...) { delete[]p1; delete[]p2; throw; } } catch (...) { delete[]p1; throw; } delete[]p1; delete[]p2; }
但是这还只是两个new,如果是三个呢,四个呢?
==所以为了解决这种类似的情况!于是有了智能指针!==
智能指针的原理
==智能指针的原理其实很简单!——我们不要手动释放!让指针出了作用域后自动的释放!==
那么如何做到的?——写一个类就好了!
template<class T> class SmartPtr { public: //构造函数在保存资源! SmartPtr(T* ptr = nullptr) :_ptr(ptr) {} //析构函数在释放资源! ~SmartPtr() { if (_ptr) delete _ptr; cout << "~SmartPtr()" << endl; } //重装* 让智能指针具有像一般指针一样的行为 T& operator*() { return *_ptr; } T* operator->() { return _ptr; } T& operator[](size_t pos) { return _ptr[pos]; } private: T* _ptr; };
==我们都知道出了作用域后,类会自动的去调用析构函数去释放资源!我们就可以通过这一特性来实现智能指针!==
#include <iostream> using namespace std; int div() { int a, b; cin >> a >> b; if (b == 0) throw invalid_argument("除0错误"); return a / b; } void Func() { int* p1 = new int[10]; SmartPtr<int> sp1(p1); int* p2 = new int[10]; SmartPtr<int> sp2(p2); *p1 = 10; p1[0]--; cout << div() << endl; } int main() { try { Func(); } catch (const exception& e) { cout << e.what() << endl; } return 0; }
如果p2抛异常,p1出了作用域也会自动释放!不用担心!
RAII
上面的这种思想我们称之为RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。
Resource Acquisition Is Initialization 意思就是资源获取即初始化!——这个初始化就是构造函数!(就是说当获取到一个需要释放了资源的时候,不要自己手动管理!而是通过一个对象进行管理!当和对象绑定后,这个资源就和对象的生命周期绑定了!)
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做 法有两大好处:
- 不需要显式地释放资源
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
==智能指针分为两个部分——一个就是RAII,一个值像指针的行为!==
template<class T> class SmartPtr { public: //******************************************************* //这一部分就是RAII //构造函数在保存资源! SmartPtr(T* ptr = nullptr) :_ptr(ptr) {} //析构函数在释放资源! ~SmartPtr() { if (_ptr) delete _ptr; cout << "~SmartPtr()" << endl; } //************************************************************** //第二部分 //重装* 让智能指针具有像一般指针一样的行为 T& operator*() { return *_ptr; } T* operator->() { return _ptr; } T& operator[](size_t pos) { return _ptr[pos]; } private: T* _ptr; };
STL中的智能指针
STL库中给我们提供了四种不同类型的智能指针!分别是auto_ptr,unique_ptr,weak_ptr,shared_ptr接下来将会一一介绍
都在momory这个头文件里面!
std::auto_ptr
auto_ptr是STL库中最早引入的智能指针!也是最臭名昭著的智能指针!——它具有十分的多的缺陷!
template<class T> class SmartPtr { public: SmartPtr(T* ptr = nullptr) :_ptr(ptr) {} ~SmartPtr() { if (_ptr) delete _ptr; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } T& operator[](size_t pos) { return _ptr[pos]; } private: T* _ptr; }; //这是我上面写的智能指针!——它有一个很大的问题!——拷贝! int main() { SmartPtr<int> sp(new int); SmartPtr<int> sp2(sp); return 0; }
因为默认生成的拷贝构造是一个浅拷贝!这就是导致了sp与sp2都是维护的是同一个的地址!所以一旦出了作用域!就会导致同一块空间被释放两次!
解决办法很多——例如写一个计数器!
==这里不可以深拷贝!我们模拟的是原生指针的行为!两个指针指向之间的拷贝本身就是深拷贝!==
==但是auto_ptr解决这个的问题的方式是——所有权的转移!==
//这是一种非常荒唐的解决办法 #include <memory> using namespace std; int main() { auto_ptr<int> ap(new int); auto_ptr<int> ap1(ap); return 0; }
==这个就会导致如果不知道的人会直接对空指针进行解引用!——即对象悬空问题!==
==赋值也是一样的!也会导致管理权的转移!==
==所以不要去使用!auto_ptr!这是一个非常危险的东西==
auto_ptr的底层实现
namespace MySTL { template<class T> class auto_ptr { public: auto_ptr(T* ptr = nullptr) :_ptr(ptr) {} ~auto_ptr() { if (_ptr) delete _ptr; } auto_ptr(auto_ptr<T>& ap)//不可以加上const,这会导致ap._ptr = nullptr;编译不通过 :_ptr(ap._ptr) { ap._ptr = nullptr; } T &operator*() { return *_ptr; } T *operator->() { return _ptr; } private: T *_ptr; }; }
==auto_ptr的实现很简单,就是一个简单的置空就好了==
unique_ptr
这个智能指针的前身是boost库里面的scope_ptr,后续进入了STL库中改名为unique_ptr
==unique_ptr对于解决拷贝的方式非常的简单粗暴!==
int main() { unique_ptr<int> up1(new int(10)); unique_ptr<int> up2(up1); up2 = up1; return 0; }
它直接禁掉了拷贝构造和赋值!不让拷贝构造也不让赋值!——就像是它的名字一样unique(唯一)
unique_ptr的底层实现
template <class T> class unique_ptr { public: unique_ptr(T* ptr = nullptr) :_ptr(ptr) {} ~unique_ptr() { if (_ptr) delete _ptr; } //直接全部禁掉! unique_ptr(unique_ptr<T>& up) = delete; unique_ptr<T>& operator=(unique_ptr<T>& up) = delete; T &operator*() { return *_ptr; } T *operator->() { return _ptr; } private: T *_ptr; };
shared_ptr
不让拷贝终究不能解决问题,所以就有了shared_ptr——这个智能指针的解决办法就是使用==引用计数!==
int main() { shared_ptr<int> sp1(new int(10)); shared_ptr<int> sp2(sp1); (*sp1)++; cout << *sp1 << endl; cout << &(*sp1) << endl; cout << &(*sp2) << endl; return 0; }
==我们可以看到就可以进行正常的拷贝了!==
引用计数的实现
首先我们要先看一下引用计数应该如何实现
template<class T> class shared_ptr { //... // private: T *_ptr; size_t count;//这样写就是一个典型的错误! };
==为什么我们不能怎么写!因为这样子计数器就是互相独立的!==
==所以我们需要一个统一的计数器!==
template<class T> class shared_ptr { //... // private: T *_ptr; static size_t count;//既然如此我们搞一个静态的行不行? };
==这就导致了只能管理一个内存块!而不是多个内存块!我们如果释放sp1,sp2后但是因为count不为0所以就不会释放!它们指向的内存块!这就已经造成了内存泄漏!==
那么我们应该怎么解决这个问题?一个资源必须匹配一个引用计数!
有很多解决的办法——例如我们写一个静态的map,将一个内存地址和计数器当成是kv键值对!
static map<T* ptr,int count>;
不要使用vector< int >,因为这样子不好找是哪个内存块对应哪个计数器
==而在STL库中是使用如下的方法解决的==
==如果某个对象析构就在那个指向的空间--就可以了==
shared_ptr的底层实现
template<class T> class shared_ptr { public: shared_ptr(T* ptr = nullptr) :_ptr(ptr), _pcount(new int(1)) {} shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr), _pcount(sp._pcount) { ++(*_pcount); } ~shared_ptr() { if(--*(_pcount) == 0) { delete _ptr; delete _pcount; } } T &operator*() { return *_ptr; } T *operator->() { return _ptr; } private: T *_ptr; int* _pcount; }; int main() { MySTL:: shared_ptr<int> sp1(new int(10)); MySTL::shared_ptr<int> sp2(sp1); MySTL::shared_ptr<int> sp3(new int(10)); return 0; }
==shared_ptr的难点是赋值重装!==
shared_ptr<T>& operator=(const shared_ptr<T>& sp) { _ptr = sp._ptr; _pcount = sp._count; ++(*_pcount); return *this; }
==这样写就是出现问题了!——已经出现内存泄漏了!==
如果我们简单的像上面写,那么就很容易导致内存泄露!
shared_ptr<T>& operator=(const shared_ptr<T>& sp) { if(_ptr != sp._ptr)//要考虑到自己给自己赋值 { if(--(*_pcount) == 0) { delete _ptr;//释放掉这个指针原本的指向的内存 delete _pcount; } _ptr = sp._ptr; _pcount = sp._pcount; ++(*_pcount);//将新指向的代码块的引用计数++ } return *this; }
==然后我们精简一下代码就变成了如下的==
template<class T> class shared_ptr { public: shared_ptr(T* ptr = nullptr) :_ptr(ptr), _pcount(new int(1)) {} shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr), _pcount(sp._pcount) { ++(*_pcount); } void release()//将相同的部分写成一个函数 { if(--(*_pcount) == 0) { delete _ptr; delete _pcount; } } ~shared_ptr() { release(); } shared_ptr<T>& operator=(const shared_ptr<T>& sp) { if(_ptr != sp._ptr)//要考虑到地址相同的情况赋值! { release(); _ptr = sp._ptr; _pcount = sp._pcount; ++(*_pcount);//将新指向的代码块的引用计数++ } return *this; } T &operator*() { return *_ptr; } T *operator->() { return _ptr; } private: T *_ptr; int* _pcount; };
shared_ptr的缺点
线程安全问题
==如果出现shared_ptr管理的内存被两个线程同时管理!==
class shared_ptr { public: //..... //我们可以写一个函数来获取_pcount的值 int use_count() { return *_pcount; } private: T *_ptr; int* _pcount; }; void test_shared_ptr() { int n = 1000; shared_ptr<int> sp(new int(1)); thread t1([&]() { for (int i = 0; i < n; ++i) { shared_ptr<int> sp2(sp); } }); thread t2([&]() { for (int i = 0; i < n; ++i) { shared_ptr<int> sp3(sp); } }); t1.join(); t2.join(); } int main() { test_shared_ptr(); }
==上面程序运行的结果!——什么都有可能!甚至还有可能报错!==
因为++,--的操作不是原子的!所以这就导致了在多线程的情况下有很强的随机性!
==解决办法!——使用加锁或者使用原子操作的++/--接口!==
template<class T> class shared_ptr { public: shared_ptr(T* ptr = nullptr) :_ptr(ptr), _pcount(new int(1)), _pmut(new mutex) {} shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr), _pcount(sp._pcount), _pmut(sp._pmut) { _pmut->lock(); ++(*_pcount); _pmut->unlock(); } void release() { _pmut->lock(); if(--(*_pcount) == 0) { delete _ptr; delete _pcount; } _pmut->unlock(); } ~shared_ptr() { release(); } shared_ptr<T>& operator=(const shared_ptr<T>& sp) { if(_ptr != sp._ptr)//要考虑到地址相同的情况赋值! { release(); _ptr = sp._ptr; _pcount = sp._pcount; _pmut = sp._pmut; _pmut->lock(); ++(*_pcount);//将新指向的代码块的引用计数++ _pmut->unlock(); } return *this; } T &operator*() { return *_ptr; } T *operator->() { return _ptr; } int use_count() { return *_pcount; } private: T *_ptr; int* _pcount; mutex* _pmut; //我们这里要使用指针!而不是mutex这个对象! //因为我们要保证所有的shared_ptr都在同一个锁下面! //如果在不同的锁下面我们就不能保证每一次只会对一个shared_ptr进行++/--了! };
==这样写代码就可以正常的运行了!==
==使用锁又引入了另一个问题——释放!==
void release() { _pmut->lock(); if(--(*_pcount) == 0) { delete _ptr; delete _pcount; delete _pmut; } _pmut->unlock(); }
如果直接怎么释放就会出现问题!
==如果我们释放了!那么我们如何解锁?==
我们可以发现会直接崩溃!
==那么我们该如如何去释放呢?——其实也很简单!做一个判断就可以!==
void release() { bool flag = false;//因为这是一个局部变量!所以不用担心线程安全问题! _pmut->lock(); if(--(*_pcount) == 0) { delete _ptr; delete _pcount; flag = true; } _pmut->unlock(); //解锁完毕之后再进行释放就可以了! if (flag) { delete _pmut; } }
==shared_ptr仅仅保护的是引用计数的线程安全!但是不保证资源的线程安全!==
struct Date { int _year = 0; int _month = 0; int _day = 0; }; void test_shared_ptr() { int n = 200000; shared_ptr<Date> sp(new Date); thread t1([&]() { for (int i = 0; i < n; ++i) { shared_ptr<Date> sp1(sp); sp1->_day++; sp1->_month++; sp1->_year++; } }); thread t2([&]() { for (int i = 0; i < n; ++i) { shared_ptr<Date> sp2(sp); sp2->_day++; sp2->_month++; sp2->_year++; } }); t1.join(); t2.join(); cout << sp.use_count() << endl; cout<< sp->_day <<endl; cout<< sp->_month <<endl; cout<< sp->_year <<endl; } int main() { test_shared_ptr(); }
==我们可以看到资源的线程安全shared_ptr是无法保证的!因为shared_ptr只能保护里面的数据访问!但是外面的是无法保护的!我们想要让其实线程安全的只能手动的加锁!(或者调用原子类的++/--)==
void test_shared_ptr() { int n = 200000; shared_ptr<Date> sp(new Date); mutex mux; //这也是lambda表达式的一个优势不用进行传参!直接引用捕抓就可以! thread t1([&]() { for (int i = 0; i < n; ++i) { shared_ptr<Date> sp1(sp); mux.lock(); sp1->_day++; sp1->_month++; sp1->_year++; mux.unlock(); } }); thread t2([&]() { for (int i = 0; i < n; ++i) { shared_ptr<Date> sp2(sp); mux.lock(); sp2->_day++; sp2->_month++; sp2->_year++; mux.unlock(); } }); t1.join(); t2.join(); cout << sp.use_count() << endl; cout<< sp->_day <<endl; cout<< sp->_month <<endl; cout<< sp->_year <<endl; } int main() { test_shared_ptr(); }
总结
shared_ptr本身是线程安全的!(拷贝和析构,进行引用计数的++/--是线程安全的!)但是==shared_ptr管理资源的访问不是线程安全的!用的时候我们要自己手动加锁保护!==
std库里面的实现的比我们更加的复杂但是也是一样的!
循环引用
shared_ptr的另一个死穴就是循环引用的问题!
struct ListNode { int _data; ListNode* _next; ListNode* _prev; ~ListNode()//写这个意义在于看节点有没有释放! { cout << "~ListNode()" << endl; } }; void test_shared_ptr_tow() { /* ListNode* n1 =new(ListNode); ListNode* n2 =new(ListNode); n1->_next = n2; n1->_prev = n1; delete n1; delete n2;*/ //我们如何将上面的改成智能指针呢? shared_ptr<ListNode> spn(new ListNode); shared_ptr<ListNode> spn2(new ListNode); spn->_next = spn2; spn2->_prev = spn; //这样写是错误的!因为spn是只能指针类型! //但是spn->_next/_prev都是原生指针类型!类型不匹配! }
//我们可以修改一下_prev和_next的类型! struct ListNode { int _data; shared_ptr<ListNode> _next; shared_ptr<ListNode> _prev; ~ListNode()//写这个意义在于看节点有没有释放! { cout << "~ListNode()" << endl; } }; void test_shared_ptr_tow() { shared_ptr<ListNode> spn(new ListNode); shared_ptr<ListNode> spn2(new ListNode); spn->_next = spn2; spn2->_prev = spn; }
==我们发现了一个问题!——为什么没有释放?==
void test_shared_ptr_tow() { shared_ptr<ListNode> spn(new ListNode); shared_ptr<ListNode> spn2(new ListNode); /*spn->_next = spn2; spn2->_prev = spn;*/ //如果删掉这两句就可以了! }
==因为刚刚那两句造成了循环引用!(而循环引用则导致了内存泄露的发生!)==
==这就形成了一个死结!_next要被销毁!就要先让 _prev先被销毁! _prev要被销毁首先要 _next先被销毁!==
==只要有两个类!出现两个类里面的对象互相管理着彼此!那么就会导致循环引用的问题!==
解决这个的办法就是我们不要让它==参与管理!指向就好了==
为了解决这个问题于是有了weak_ptr
weak_ptr
==weak_ptr的作用就是解决shared_ptr的循环引用的问题!==
==weak_ptr不支持的指针的构造!说明了weak_ptr不能独立进行管理!——weak_ptr是不支持RAII的!==
但是是支持shared_ptr的构造!
weak_ptr是可以进行指向资源,也可以访问资源!但是不进行管理资源!——不会增加引用计数!
struct ListNode { int _data; //std::shared_ptr<ListNode> _next; //std::shared_ptr<ListNode> _prev; std::weak_ptr<ListNode> _next; std::weak_ptr<ListNode> _prev; ~ListNode()//写这个意义在于看节点有没有释放! { cout << "~ListNode()" << endl; } }; void test_shared_ptr_tow() { std::shared_ptr<ListNode> spn(new ListNode); std::shared_ptr<ListNode> spn2(new ListNode); spn->_next = spn2; spn2->_prev = spn; cout << spn.use_count() << endl; cout << spn2.use_count() << endl; }
weak_ptr的底层实现
template<class T> class shared_ptr { public: //.... T* get()const { return _ptr; } //... private: T *_ptr; int* _pcount; mutex* _pmut; }; template<class T> class weak_ptr { public: weak_ptr() :_ptr(nullptr) {} weak_ptr(const shared_ptr<T>& sp)//这个是重点! :_ptr(sp.get())//因为不在同一个的类里面所我们要在shared_ptr里面写一个get { } weak_ptr<T> operator=(const weak_ptr<T>& wp) { _ptr = wp._ptr; return *this; } weak_ptr<T> operator=(const shared_ptr<T>& sp)//这个也是重点 { _ptr = sp.get(); return *this; } T &operator*() { return *_ptr; } T *operator->() { return _ptr; } private: T *_ptr; //库里面的也是需要计数的!因为要记录这个weak_ptr是否失效! //我们这里只是简略的实现一个! };
==样子最简单的一个weak_ptr就完成了!==
定制删除器
==上面的我们都没有考虑到一个问题==
share_ptr<int> sp(new int[10]); //遇到这种情况我们应该怎么办? //我们里面调用的是delete!但是对于数组我们一般要求delete[] 如果不匹配对于内置类型! //那么也没有什么问题!如果是自定义类型那么就会导致内存泄漏!甚至会程序崩溃! //所以我们该如何解决这个问题?
==所以我们需要使用定制删除器来解决这个问题我们可以看一下STL库中的定制删除器是是什么样子==
//定制删除器 template<class T> struct DeleteArray { void operator()(const T* ptr) { delete[] ptr; cout << "delete[]" << endl;//这个是方便我们用来看的 } };
==其实这个定制删除器听上去很高大上!但是本质就是一个仿函数!==
int main() { std::shared_ptr<int> sp1(new int[10],DeleteArray<int>()); std::shared_ptr<std::string> sp2(new std::string[10],DeleteArray<std::string>()); std::shared_ptr<std::string> sp3(new std::string[10], [](const string* ptr) {delete[] ptr; cout << "delete[]" << endl;});//或者我们可以使用lambda表达式!(lambda表达式的底层也是一个仿函数) return 0; }
std::shared_ptr<FILE> sp3(fopen("test.txt", "wb"), [](FILE* fp) {fclose(fp); });
==出了上面的的释放数组!还可以去关闭文件!——这就是定制删除器的意义==
定制删除器的实现
标签:11,return,int,sp,C++,pcount,字长,shared,ptr From: https://blog.51cto.com/u_15835985/7454574==我们在自己的shared_ptr中的是不能像库中在构造函数的时候去传入定制删除器的!因为我们的自己实现的仅仅只是一个类,而库里面其实是由数个类去实现share_ptr的!==
template<class T> class shared_ptr { public: //.... template<class D> shared_ptr(T* ptr, D del)//这里有一个很大的问题! :_ptr(sp._ptr), _pcount(sp._pcount), _pmut(sp._pmut), _del(del)//这样写看上去没有问题!但是!这个D是属于构造函数的!类成员中如果我们用了D类型就会报错! { _pmut->lock(); ++(*_pcount); _pmut->unlock(); } //而这个删除器是析构函数要去使用的! //所以我们没有办法在构造函数里面传入del //库里面是使用另一个类来解决这个问题的! //.... private: T *_ptr; int* _pcount; mutex* _pmut; D _del;//这个会报错!因为D仅仅属于构造函数!我们无法定义_del };
==所以我们只能怎么实现==
namesapce MySYL { template<class T> struct Defult_delete { void operator(T* ptr) { delete ptr; } }//我们可以写一个默认的模板参数!这样子就可以不用每次都传入了! //需要释放数组的时候再传入!而不是用默认的! template<class T,class D = Defult_delete<T>> class shared_ptr { public: shared_ptr(T* ptr = nullptr) :_ptr(ptr), _pcount(new int(1)), _pmut(new mutex) {} shared_ptr(const shared_ptr<T,D>& sp) :_ptr(sp._ptr), _pcount(sp._pcount), _pmut(sp._pmut) { _pmut->lock(); ++(*_pcount); _pmut->unlock(); } void release() { bool flag = false; _pmut->lock(); if(--(*_pcount) == 0) { D del; del(_ptr); delete _pcount; flag = true; } _pmut->unlock(); if (flag) { delete _pmut; } } ~shared_ptr() { release(); } shared_ptr<T,D>& operator=(const shared_ptr<T,D>& sp) { if(_ptr != sp._ptr)//要考虑到地址相同的情况赋值! { release(); _ptr = sp._ptr; _pcount = sp._pcount; _pmut = sp._pmut; _pmut->lock(); ++(*_pcount);//将新指向的代码块的引用计数++ _pmut->unlock(); } return *this; } T &operator*() { return *_ptr; } T *operator->() { return _ptr; } int use_count()const { return *_pcount; } T* get()const { return _ptr; } private: T *_ptr; int* _pcount; mutex* _pmut; }; } template<class T> struct DeleteArray { void operator()(const T* ptr) { delete[] ptr; cout << "delete[]" << endl; } };//手动写一个定制删除器 int main() { MySTL::shared_ptr<int,DeleteArray<int>> sp1(new int[10]); MySTL::shared_ptr<std::string, DeleteArray<std::string>> sp2(new std::string[10]); // MySTL::shared_ptr<FILE,[](FILE* ptr) {fclose(ptr); } > sp3(fopen("test.cpp", "r")); //这样写就是不可以的!因为我们要传的是类型!而lambda表达式其实是一个匿名对象! // MySTL::shared_ptr < FILE, decltype([](FILE* ptr) {fclose(ptr); }) > sp3(fopen("test.cpp", "r")); //decltype能够推导表达式的类型!那么我们能不能怎么写呢?——不行!因为decltype还在运行的时候推导的! //但是模板要求要编译时期就传入类型! //所以我们这样实现有很多的缺陷 return 0; }
库里面的unique_ptr也是和我们一样使用这种方式来实现定制删除器!同样的也有一样的问题!
struct FClose { void operator()(FILE* ptr) { fclose(ptr); cout << "fclose" << endl; } }; int main() { //std::unique_ptr<FILE, decltype([](FILE* ptr) {fclose(ptr);})> up(fopen("test.cpp","r"));//这样写是错误的! std::unique_ptr<FILE, FClose> up2(fopen("test.cpp", "r"));//这样写是可以的! return 0; }