目录
RAII
RAII(Resource Acquisition Is Initialization)根据对象的生命周期控制,初始化构造对象时管理资源,销毁对象在对象析构时释放资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。这么做的好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if(_ptr)
delete _ptr;
}
private:
T* _ptr;
};
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
// 讲tmp指针委托给了sp对象,用时老师的话说给tmp指针找了一个可怕的女朋友!天天管着你,直到你go die
SmartPtr<int> sp(tmp);
// _MergeSort(a, 0, n - 1, tmp);
// 这里假设处理了一些其他逻辑
vector<int> v(1000000000, 10);
// ...
}
int main()
{
try {
int a[5] = { 4, 5, 2, 3, 1 };
MergeSort(a, 5);
}
catch(const exception& e)
{
cout<<e.what()<<endl;
}
return 0;
}
注意:并没有实现智能指针,只是实现了一个可以管理资源的类。
模拟实现SmartPtr
模拟实现SmartPtr:
智能指针:1.实现RAII思想,2.实现指针的操作(-> 和 *)
//智能指针:1.实现RAII思想,2.实现指针的操作
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
{
delete _ptr;
cout << "delete" << endl;
_ptr = nullptr;
}
}
//模拟实现指针功能: * 和 ->
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int main()
{
//常规的指针
int* pi = new int(2);
cout << *pi << endl;
*pi = 3;
cout << *pi << endl;
//普通指针malloc后需要手动释放
delete pi;
//智能指针
cout << "SmartPtr:" << endl;
SmartPtr<int> sp(new int(10));
cout << *sp << endl;
*sp = 20;
cout << *sp << endl;
//智能指针编译器会在指针对象使用完成后,进行自动调用析构函数进行释放。
return 0;
}
智能指针的原理:
- 1. RAII特性
- 2. 重载operator*和opertaor->,具有像指针一样的行为。
常见的三种智能指针
auto_ptr
模拟测试 C++98版本的库提供的auto_ptr
在使用库里面提供的智能指针,在进行多个智能指针对象管理同一份资源,仅被释放一次。
class A
{
public:
int _a = 1;
int _b = 2;
int _c = 3;
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
//多个智能指针对象管理同一份资源,仅被释放一次。
//一般禁止使用
auto_ptr<A>sp3(new A());//库里的智能指针会优化,对多个对象管理一份资源只释放一次
sp3->_a = 20;
cout << sp3->_a << endl;
auto_ptr<A>copy(sp3);
copy->_a = 100;
cout << copy->_a << endl;
auto_ptr<A>copy2(copy);
copy2->_b = 1;
cout << copy2->_b << endl;
return 0;
}
假如我们使用自己上面写的SmartPtr进行多个对象管理同一份资源时,在使用结束后就会出错,出现二次释放问题。
#include<iostream>
// C++库中的智能指针都定义在memory这个头文件中
#include<memory>
using namespace std;
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
{
delete _ptr;
cout << "delete" << endl;
_ptr = nullptr;
}
}
//模拟实现指针功能: * 和 ->
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
class A
{
public:
int _a = 1;
int _b = 2;
int _c = 3;
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
//使用自己定义的智能指针多次释放
SmartPtr<A>sp3(new A());
sp3->_a = 20;
cout << sp3->_a << endl;
SmartPtr<A> copy (sp3);//假如多个我们自己实现智能指针同时管理一份资源,就会造成资源二次释放。
copy->_a = 100;
SmartPtr<A> copy2 (copy);
copy2->_b = 1;
//system("pause");
return 0;
}
//测试 系统中auto_ptr智能指针,在对同一块空间被多个对象使用会不会造成程序奔溃问题
#include<iostream>
// C++库中的智能指针都定义在memory这个头文件中
#include<memory>
using namespace std;
class A
{
public:
int _a = 1;
int _b = 2;
int _c = 3;
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
auto_ptr<A>sp3(new A());//库里的智能指针
sp3->_a = 20;
cout << sp3->_a << endl;
auto_ptr<A>copy(sp3);//这里发生一次拷贝,顺便将资源的管理权交给新的对象,它不会再拥有管理资源的能力
//sp3已经完成对资源管理权的转移,因此无法访问到_a成员,sp3指针被悬空,因此在这里程序会崩溃,空指针无法解引用。
//指针悬空
//sp3->_a = 1;
copy->_a = 100;
auto_ptr<A>copy2(copy);
//管理权转移,指针悬空,因此不能解引用访问。
//copy->_b = 1;
copy2->_b = 1;
return 0;
}
假如在上面程序中我们去掉//sp3->_a = 1; 和 //copy->_b = 1;注释,运行程序结果如下:
崩溃原因:sp3已经完成对资源管理权的转移,因此它便无法访问到_a成员,sp3指针因此会被悬空,所以在这里程序会崩溃,空指针无法解引用。下面的copy原因同理类似。
auto_pt模拟实现
auto_ptr的实现原理:实现管理权转移的思想
拷贝时发生管理权转移,会导致当前被拷贝的智能指针悬空,最终会导致智能指针访问异常,程序崩溃,因此auto_ptr 禁止使用。
//模拟实现auto_ptr智能指针: 解决之前的auto_ptr存在二次释放问题
template<class T>
class AutoPtr {
public:
AutoPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~AutoPtr()
{
if (_ptr)
{
delete _ptr;
cout << "delete" << endl;
_ptr = nullptr;
}
}
// 一旦发生拷贝,就将ap中资源转移到当前对象中,然后另ap与其所管理资源断开联系,
// 这样就解决了一块空间被多个对象使用而造成程序奔溃问题
//浅拷贝,指针只是管理资源,不拥有资源
//管理权转移 拷贝构造
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
//赋值运算符重载
AutoPtr<T>& operator=(AutoPtr<T>& ap)
{
// 检测是否为自己给自己赋值
if (this != ap)
{
// 释放当前对象中资源
if (_ptr)
delete _ptr;
//管理权转移
// 转移ap中资源到当前对象中
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
//实现指针功能:解引用 ->
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
class A
{
public:
int _a = 1;
int _b = 2;
int _c = 3;
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
AutoPtr<A> sp4(new A());
sp4->_a = 10;
AutoPtr<A> copy3(sp4);
copy3->_a = 100;
AutoPtr<A> copy4(copy3);
copy4->_b = 1;
//只会调用一次析构 一次 delete
//system("pause");
return 0;
}
unique_ptr
使用库里面的unique_ptr
#include<iostream>
// C++库中的智能指针都定义在memory这个头文件中
#include<memory>
using namespace std;
int main()
{
unique_ptr<A> up(new A());
up->_a = 10;
unique_ptr<A> up2(new A());
//使用库里面unique_ptr:它禁止相互间的赋值行为,防赋值
up2 = up;
//使用库里面unique_ptr:它禁止相互间的拷贝行为,防拷贝
unique_ptr<A> copy(up);
return 0;
}
我们可以看到库里面的unique_ptr是防拷贝,防赋值。
模拟实现unique_ptr
只需要在上面实现SmartPtr 基础上禁止掉拷贝构造和赋值运算符重载函数。而在禁止掉拷贝构造和赋值运算符重载函数:有两种方法 {1}使用C++11语法delete {2}将两个函数定义为私有,只声明不实现
#include<iostream>
using namespace std;
//模拟实现Unique_Ptr:只需要禁止掉拷贝构造和赋值运算符重载函数
template<class T>
class UniquePtr {
public:
UniquePtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~UniquePtr()
{
if (_ptr)
{
delete _ptr;
cout << "delete" << endl;
_ptr = nullptr;
}
}
//禁止掉拷贝构造和赋值运算符重载函数:有两种方法 {1}使用C++11语法delete {2}将两个函数定义为私有,只声明不实现
//禁止拷贝构造
//C++11语法 声明成delete函数
UniquePtr(UniquePtr<T>& ap) = delete;
//C++11语法
//禁止赋值运算符重载 声明成delete函数
UniquePtr<T>& operator=(UniquePtr<T>& ap) = delete;
//实现指针功能:解引用 还有 ->
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
//定义成私有,只声明不实现
//防拷贝构造
UniquePtr(UniquePtr<T>& ap);
//防赋值运算符重载
UniquePtr<T>& operator=(UniquePtr<T>& ap);
T* _ptr;
};
class A
{
public:
int _a = 1;
int _b = 2;
int _c = 3;
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
//使用自己实现的UniquePtr测试拷贝和赋值操作
UniquePtr<A> up3(new A());
UniquePtr<A> copy2(up3);
UniquePtr<A> up4(new A());
up4 = up3;
//system("pause");
return 0;
}
unique_ptr特点:防拷贝,防赋值,安全的,可以使用。缺陷:不能进行拷贝赋值。
shared_ptr
使用库里面的shared_ptr
#include<iostream>
// C++库中的智能指针都定义在memory这个头文件中
#include<memory>
using namespace std;
class A
{
public:
int _a = 1;
int _b = 2;
int _c = 3;
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
shared_ptr<A> sp(new A());
//查看引用计数,看当前资源被几个指针管理
cout << sp.use_count() << endl;//1
//可以拷贝
shared_ptr<A> cope(sp);
cout << cope.use_count() << endl;//2
//可以赋值
shared_ptr<A> sp2(new A());
cout << sp2.use_count() << endl;//1
sp2 = sp;
cout << sp2.use_count() << endl;//3
//程序结束释放两次delte
system("pause");
return 0;
}
我们发现系统提供的shared_ptr是完全安全的。
模拟实现shared_ptr
我们在shared_ptr模拟实现:在释放资源时采用引用计数方法,因此在++引用计数或者--引用计数时必须保证原子性操作,因此我们使用了 mutex* _mtx 进而保证原子性操作
#include<mutex>
#include<iostream>
using namespace std;
//shared_ptr模拟实现:释放时采用引用计数方法
template<class T>
class SharedPtr{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pcount(new int(1))
, _mtx(new mutex())
{}
SharedPtr(SharedPtr<T>& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
, _mtx(sp._mtx)
{
_mtx->lock();
++(*_pcount);
_mtx->unlock();
}
SharedPtr<T>& operator=(SharedPtr<T>& sp)
{
//if (this != sp)
if (_ptr != sp._ptr)
{
_mtx->lock();
--(*_pcount);
_mtx->unlock();
if (*_pcount == 0)
{
delete _pcount;
delete _ptr;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
_mtx->lock();
++(*_pcount);
_mtx->unlock();
}
return *this;
}
~SharedPtr()
{
_mtx->lock();
--(*_pcount);
_mtx->unlock();
if (*_pcount == 0)
{
if (_ptr)
{
delete _ptr;
delete _pcount;
cout << "delete _ptr" << endl;
_ptr = nullptr;
_pcount = nullptr;
}
}
}
//实现指针功能:解引用 还有 ->
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int useCount()
{
return *_pcount;
}
private:
T* _ptr;
int *_pcount;
//不能给成静态的 不能使用一个引用计数表示所有资源被引用的数量,应该每一个资源都拥有自己的引用计数
//static int _count;
mutex* _mtx;//保证原子性操作
};
//template<class T>
//int SharedPtr<T>::_count = 0;
int main()
{
SharedPtr<int> sp(new int(1));
SharedPtr<int> sp2(new int(2));
cout << sp.useCount() << endl;//1
cout << sp2.useCount() << endl;//1
SharedPtr<int> copy(sp);
cout << sp.useCount() << endl;//2
cout << copy.useCount() << endl;//2
sp2 = sp;
cout << sp.useCount() << endl;//3
cout << sp2.useCount() << endl;//3
copy = sp;
cout << copy.useCount() << endl;//3
cout << sp.useCount() << endl;//3
system("pause");
return 0;
}
在多线程条件下的shared_ptr
#prama once //防止头文件多次引用
#include<mutex>
#include<thread>
#include<iostream>
using namespace std;
// C++库中的智能指针都定义在memory这个头文件中
#include<memory>
template<class T>
class SharedPtr{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pcount(new int(1))
, _mtx(new mutex())
{}
SharedPtr(SharedPtr<T>& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
, _mtx(sp._mtx)
{
/*_mtx->lock();
++(*_pcount);
_mtx->unlock();*/
addRef();
}
SharedPtr<T>& operator=(SharedPtr<T>& sp)
{
//if (this != sp)
if (_ptr != sp._ptr)
{
/*_mtx->lock();
--(*_pcount);
_mtx->unlock();*/
if (subRef() == 0)
{
delete _pcount;
delete _ptr;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
/*_mtx->lock();
++(*_pcount);
_mtx->unlock();*/
addRef();
}
return *this;
}
~SharedPtr()
{
/*_mtx->lock();
--(*_pcount);
_mtx->unlock();*/
if (subRef() == 0)
{
if (_ptr)
{
delete _ptr;
delete _pcount;
cout << "delete _ptr" << endl;
_ptr = nullptr;
_pcount = nullptr;
}
}
}
//实现指针功能:解引用 还有 ->
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int useCount()
{
return *_pcount;
}
int addRef()
{
_mtx->lock();
++(*_pcount);
_mtx->unlock();
return *_pcount;
}
int subRef()
{
_mtx->lock();
--(*_pcount);
_mtx->unlock();
return *_pcount;
}
private:
T* _ptr;
int *_pcount;
//不能给成静态的 不能使用一个引用计数表示所有资源被引用的数量,应该每一个资源都拥有自己的引用计数
//static int _count;
mutex* _mtx;//保证原子性操作
};
#include<thread>
mutex mtx;
//使用自己定义的 SharedPtr
void fun(SharedPtr<int>& sp, int n)
{
for (int i = 1; i <= n; ++i)
{
mtx.lock();
++(*sp);
mtx.unlock();
SharedPtr<int> copy(sp);
}
}
void test1()
{
int n = 10000;
SharedPtr<int> sp(new int(0));
thread t1(fun, sp, n);
thread t2(fun, sp, n);
t1.join();
t2.join();
cout << *sp << endl;//20000
cout << sp.useCount() << endl;//1
}
//使用库里面的 shared_ptr
void fun2(shared_ptr<int>sp, int n)
{
for (int i = 0; i < n; ++i)
{
mtx.lock();
++(*sp);
mtx.unlock();
shared_ptr<int> copy(sp);
}
}
void test2()
{
int n = 10000;
shared_ptr<int> sp(new int(0));
thread t1(fun2, sp, n);
thread t2(fun2, sp, n);
t1.join();
t2.join();
cout << *sp << endl;//20000
cout << sp.use_count() << endl;//1
}
int main()
{
test1();
//test2();
system("pause");
return 0;
}
在这里test1()是我们自己实现的shared_ptr ,而test2()我们使用的库里的shared_ptr 二者进行对比:
shared_ptr的循环引用问题
//循环引用 造成无法释放结点
template <class T>
class ListNode
{
public:
shared_ptr<ListNode<T>> _prev;
shared_ptr<ListNode<T>> _next;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
void test3()
{
shared_ptr<ListNode<int>> sp(new ListNode<int>());
shared_ptr<ListNode<int>> sp2(new ListNode<int>());
cout << sp.use_count() << endl;//1
cout << sp2.use_count() << endl;//1
sp->_next = sp2;
sp2->_prev = sp;
cout << sp.use_count() << endl;//2
cout << sp2.use_count() << endl;//2
//无法释放空间,没调用析构函数
}
int main()
{
test3();
system("pause");
return 0;
}
分析循环引用问题
- 1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
- 2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
- 3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
- 4. 也就是说_next析构了,node2就释放了。
- 5. 也就是说_prev析构了,node1就释放了。
- 6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。
如何解决?
//解决循环引用方法
template <class T>
class ListNode
{
public:
//weak_ptr:不会管理资源,也不会修改引用计数
weak_ptr<ListNode<T>> _prev;
weak_ptr<ListNode<T>> _next;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
void test3()
{
shared_ptr<ListNode<int>> sp(new ListNode<int>());
shared_ptr<ListNode<int>> sp2(new ListNode<int>());
cout << sp.use_count() << endl;//1
cout << sp2.use_count() << endl;//1
sp->_next = sp2;
sp2->_prev = sp;
cout << sp.use_count() << endl;//1
cout << sp2.use_count() << endl;//1
//weak_ptr: 不可以单独使用,需要借助shared_ptr进行初始化。
//weak_ptr<int> wp(new int(2));
}
int main()
{
test3();
system("pause");
return 0;
}
原理:sp->_next = sp2;和sp2->_prev = sp1;时weak_ptr的_next和_prev不会增加sp1和sp2的引用计数。
注意:weak_ptr: 不可以单独使用,需要借助shared_ptr进行初始化
shared_ptr小结
通过引用计数完成拷贝,引用计数类型为指针类型,拷贝,属于shared_ptr的成员变量,不同的资源会有一个唯一的记录资源被引用的个数的空间,因此它是是安全的,可以使用的。
智能指针如果指向同一个资源,所访问的引用计数即为同一个空间的内容。
在拷贝时进行引用计数的增加,析构时进行引用计数的减减。释放资源:引用计数为0时释放资源。
线程安全:引用计数要保证线程安全,给每一份资源设置一把锁,修改对应的资源的引用计数时,先加锁。保证计数的修改为一个串行的操作,保证线程安全。
仿函数定制删除器
如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这个问题。
//仿函数的删除器,定制删除器
class B
{
public:
~B()
{
cout << "~B()" << endl;
}
};
template <class T>
class DeleteArray
{
public:
//仿函数 : 重载括号运算符函数
void operator() (T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
template <class T>
class FreeDelete
{
public:
//仿函数 : 重载括号运算符函数
void operator() (T* ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
}
};
void test4()
{
shared_ptr<int> sp(new int[10], DeleteArray<int>());
shared_ptr<int> sp2((int*)malloc(100), FreeDelete<int>());
shared_ptr<B> sp3(new B[10], DeleteArray<B>());
shared_ptr<B> sp4((B*)malloc(100),FreeDelete<B>());
}
int main()
{
test4();
system("pause");
return 0;
}
守卫锁
守卫锁:其实也是利用RAII思想,利用对象声明周期管理锁的生命周期:构造加锁,析构解锁。
//守卫锁 也是利用RAII思想,利用对象声明周期管理锁的生命周期:构造加锁,析构解锁。
template<class Mtx>
class LockGuard
{
public:
LockGuard(Mtx& mtx)
:_mtx(mtx)
{
_mtx.lock();
}
~LockGuard()
{
cout << "~LockGuard()" << endl;
_mtx.unlock();
}
//防拷贝, 如果可以拷贝的话,导致多个对象同时解锁,而一个锁不能被解锁多次,因此需要防拷贝
LockGuard(const LockGuard<Mtx>& lg) = delete;
private:
// 注意这里必须使用引用,不支持拷贝,否则锁的就不是一个互斥量对象
Mtx& _mtx;
};
mutex mtx;
void fun()
{
int i;
cin >> i;
//mtx.lock();
LockGuard<mutex> lg(mtx);
if (i == 9)
{
return;
cout << i << endl;
}
//mtx.unlock();
}
int main()
{
thread t1(fun);
thread t2(fun);
t1.join();
t2.join();
system("pause");
return 0;
}