目录
1.RAIl(智能指针的雏形)
2.拷贝导致的问题以及智能指针发展历史
2.1拷贝的问题(资源被析构两次)
2.2auto_ptr(资源权转移,不建议使用)
2.3unique_ptr(防拷贝,在不需要拷贝的情况下使用)
2.4.2循环引用的问题(重点)
3.定制删除器
智能指针是拿来处理异常安全问题的
- 下面这段代码如果抛异常了,那么p1就指向的空间就没有被释放,内存泄漏
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void func()
{
int* p1 = new int;
cout << div() << endl; // 异常安全的问题
delete p1;
}
int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
1.RAIl(智能指针的雏形)
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源,在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。(就是把原本资源托管给一个类对象)
优势:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效(声明周期在调用栈帧里面,声明周期结束自动调析构函数)
template<class T> class SmartPtr { public: SmartPtr(T* ptr) :_ptr(ptr) {} //生命周期结束,自动析构 ~SmartPtr() { cout << "delete:" << _ptr << endl; delete _ptr; } // 像迭代器一样使用 T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; }; int div() { int a, b; cin >> a >> b; if (b == 0) throw invalid_argument("除0错误"); return a / b; } void func() { SmartPtr<int> sp1(new int); SmartPtr<int> sp2(new int); SmartPtr<int> sp3(new int); *sp1 = 10; cout << *sp1 << endl; (*sp1)++; (*sp1)++; cout << *sp1 << endl; cout << div() << endl; } int main() { try { func(); } catch (const exception& e) { cout << e.what() << endl; } return 0; }
执行结果·:
解决了如果抛异常,在堆上开的空间得不到释放,导致的内存问题;
重载operator*和opertaor->,具有像指针一样的行为,可以正常访问
2.拷贝导致的问题以及智能指针发展历史
2.1拷贝的问题(资源被析构两次)
// RAII,把资源托管给一个类对象
// 用起来像指针一样
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
// 像指针一样使用
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int main()
{
//SmartPtr<int> sp1(new int);
//SmartPtr<int> sp2(sp1);
SmartPtr<int> sp1(new int);
SmartPtr<int> sp2(new int);
sp2 = sp1;
return 0;
}
执行结果
- 默认生成的拷贝构造,赋值重载都是浅拷贝,同一块空间被析构两次,程序奔溃
不能深拷贝的原因
2.2auto_ptr(资源权转移,不建议使用)
//auto_ptr
namespace LDJ
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
//:_ptr(nullptr)
{
// 管理权转移
sp._ptr = nullptr;
//swap(_ptr, sp._ptr);
}
~auto_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
// 结论:auto_ptr是一个失败设计
int main()
{
LDJ::auto_ptr<int> sp1(new int);
LDJ::auto_ptr<int> sp2(sp1); // 管理权转移
// sp1悬空
*sp2 = 10;
cout << *sp1 << endl;
cout << *sp2 << endl;
return 0;
}
执行结果
- sp1悬空了;
2.3unique_ptr(防拷贝,在不需要拷贝的情况下使用)
- unique_ptr的实现原理:简单粗暴的防拷贝
namespace LDJ
{
template<class T, class D = default_delete<T>>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:
T* _ptr;
};
}
2.4shared_ptr
- shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一。
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
namespace LDJ
{
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pRefCount(new int(1))
{}
void AddRef()
{
++*(_pRefCount);
}
void Release()
{
if (--(*_pRefCount) == 0 && _ptr)
{
delete _ptr;
delete _pRefCount;
}
}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
, _pmtx(sp._pmtx)
{
AddRef();
}
~shared_ptr()
{
Release();
}
// 像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pRefCount;
}
}
2.4.1operator=需要处理一下
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//if (this != &sp)
if (this->_pRefCount != sp._pRefCount)
{
Release();
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
AddRef();
return *this;
}
}
2.4.2循环引用的问题(重点)
struct Node { ~Node() { cout << "~Node" << endl; } int val; std::shared_ptr<Node> _next; std::shared_ptr<Node> _prev; }; int main() { std::shared_ptr<Node> sp1(new Node); std::shared_ptr<Node> sp2(new Node); sp1->_next = sp2; sp2->_prev = sp1; return 0; }
执行结果:如果被析构了会打印两行~Node,然而并没有,说明sp1和sp2都没有被释放
循环引用的具体原因:
解决循环引用weak_ptr
- weak_ptr不是常规的智能指针,不增加计数支持访问,所以也不支持RAII
struct Node
{
~Node()
{
cout << "~Node" << endl;
}
int val;
std::weak_ptr<Node> _next;
std::weak_ptr<Node> _prev;
};
int main()
{
std::shared_ptr<Node> sp1(new Node);
std::shared_ptr<Node> sp2(new Node);
cout << "sp1Count:" << sp1.use_count() << endl;
cout << "sp2Count:" << sp2.use_count() << endl;
sp1->_next = sp2;
sp2->_prev = sp1;
cout << "sp1Count:" << sp1.use_count() << endl;
cout << "sp2Count:" << sp2.use_count() << endl;
return 0;
}
执行结果:前后sp1和sp2的计数都为1;所以可以证明weak_ptr不增加引用计数且解决了循环引用的问题;
3.定制删除器
那么如果资源不是new出来的呢?比如:new[]、malloc、fopen,delete就不够用了
template<class T>
struct DeleteArray
{
void operator()(const T* ptr)
{
delete[] ptr;
}
};
struct DeleteFile
{
void operator()(FILE* ptr)
{
fclose(ptr);
}
};
unique_ptr<A> up1(new A);
unique_ptr<A, DeleteArray<A>> up2(new A[10]);
unique_ptr<FILE, DeleteFile> up3(fopen("test.txt", "w"));