目录
智能指针作用
代码
auto_ptr
特点
模拟实现
unique_ptr
模拟实现
模拟实现
解决方式:加锁
代码
总结
循环引用
weak_ptr就可以解决这个问题
代码
模拟实现
定制删除器
更好的解决了多个异常捕获不好解决的状况
#include
using namespace std;
template
class SmartPtr
{
public:
// 保存资源
SmartPtr(T* ptr)
:_ptr(ptr)
{ }
//释放资源
~SmartPtr()
{
delete[] _ptr;
cout << _ptr << endl;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
};
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw "除0错误";
return a / b;
}
void Func()
{
SmartPtr sp1(new int[10]);
SmartPtr sp2(new int[10]);
*sp1 = 10;
sp1[0]--;
cout << *sp1 << endl;
cout << div() << endl;
}
int main()
{
try
{
Func();
}
catch (const char* s)
{
cout << s << endl;
}
return 0;
}
这种方式又叫RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:1.不需要显式地释放资源。
2.采用这种方式,对象所需的资源在其生命期内始终保持有效。
拷贝构造的特点是资源转移——很多公司禁止auto_ptr。
auto_ptr ap1(new int);
auto_ptr ap2(ap1);
比如以上代码,他会把ap1给清空然后把资源转移给ap2 见图,会导致对象悬空。
// auto_ptr
template
class auto_ptr
{
public:
// 保存资源
auto_ptr(T* ptr = nullptr)
:_ptr(ptr)
{ }
//释放资源
~auto_ptr()
{
delete _ptr;
cout << _ptr << endl;
}
auto_ptr(auto_ptr& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr; // ap._ptr被置空了
}
auto_ptr& operator=(auto_ptr& ap)
{
if (this != &ap)
{
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
};
unique_ptr的实现原理:简单粗暴的防拷贝,直接把拷贝构造给封掉!
C++可以使用在拷贝构造后边加上=delete;
// unique_ptr
template
class unique_ptr
{
public:
// 保存资源
unique_ptr(T* ptr = nullptr)
:_ptr(ptr)
{ }
//释放资源
~unique_ptr()
{
delete _ptr;
cout << _ptr << endl;
}
// 给拷贝构造封了
unique_ptr(unique_ptr& up) = delete;
unique_ptr& operator=(unique_ptr& up)
{
if (this != &ap)
{
delete _ptr;
_ptr = up._ptr;
up._ptr = nullptr;
}
return *this;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
};
引用计数来解决拷贝构造问题
// shared_ptr
template
class shared_ptr
{
public:
// 保存资源
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pcount(new int(1))// 为引用计数初始化为1
{ }
//释放资源
~shared_ptr()
{
Release();
}
shared_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
{
++(*_pcount);
}
void Release()// 释放资源
{
if (--(*_pcount) == 0)// 如果当前对象的引用计数为1,则直接释放当前对象
{
cout << "delete: " << _ptr << endl;
delete _ptr;
delete _pcount;
}
}
shared_ptr& operator=(const shared_ptr& sp)
{
if (_ptr != sp._ptr) // 如果管理的是同一块资源的两个对象则不需要赋值
{
Release();
_ptr = sp._ptr; // 资源地址赋值
_pcount = sp._pcount; // 引用计数赋值
(*_pcount)++;// 引用计数++
}
}
shared_ptr& operator=(shared_ptr& sp)
{
if (this != &sp)
{
delete _ptr;
_ptr = sp._ptr;
sp._ptr = nullptr;
}
return *this;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
int* _pcount;
};
// shared_ptr
template
class shared_ptr
{
public:
// 保存资源
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pcount(new int(1))// 为引用计数初始化为1
{ }
//释放资源
~shared_ptr()
{
Release();
}
shared_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
{
++(*_pcount);
}
void Release()// 释放资源
{
if (--(*_pcount) == 0)// 如果当前对象的引用计数为1,则直接释放当前对象
{
//cout << "delete: " << _ptr << endl;
delete _ptr;
delete _pcount;
}
}
shared_ptr& operator=(const shared_ptr& sp)
{
if (_ptr != sp._ptr) // 如果管理的是同一块资源的两个对象则不需要赋值
{
Release();
_ptr = sp._ptr; // 资源地址赋值
_pcount = sp._pcount; // 引用计数赋值
(*_pcount)++;// 引用计数++
}
}
shared_ptr& operator=(shared_ptr& sp)
{
if (this != &sp)
{
delete _ptr;
_ptr = sp._ptr;
sp._ptr = nullptr;
}
return *this;
}
int use_count()
{
return *_pcount;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
int* _pcount;
};
void test_shared_ptr()
{
int n = 10000;
shared_ptr sp1(new int(1));
thread t1([&]()
{
for (int i = 0; i < n; i++)
{
shared_ptr sp2(sp1);
}
});
thread t2([&]()
{
for (int i = 0; i < n; i++)
{
shared_ptr sp3(sp1);
}
});
t1.join();
t2.join();
cout << sp1.use_count() << endl;
}
结果应该为1:因为虽然在不停的拷贝构造,但是拷贝的时候引用计数会+1,析构的时候就会-1;最终应该还为1,可结果并非我们想的那样。
见图:
可能会崩溃或者引用计数不为1
这都是线程不安全的体现
// shared_ptr
template
class shared_ptr
{
public:
// 保存资源
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pcount(new int(1))// 为引用计数初始化为1
,_pmtx(new mutex)
{}
//释放资源
~shared_ptr()
{
Release();
}
shared_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
,_pmtx(sp._pmtx)
{
_pmtx->lock(); // t1 t2
++(*_pcount);
_pmtx->unlock();
}
void Release()// 释放资源
{
bool flag = false;
_pmtx->lock();
if (--(*_pcount) == 0)// 如果当前对象的引用计数为1,则直接释放当前对象
{
//cout << "delete: " << _ptr << endl;
delete _ptr;
delete _pcount;
flag = true;
//delete _pmtx;
}
_pmtx->unlock();
// 如果flag==true说明引用计数减到0了
if (flag == true)
{
delete _pmtx;
}
}
shared_ptr& operator=(const shared_ptr& sp)
{
if (_ptr != sp._ptr) // 如果管理的是同一块资源的两个对象则不需要赋值
{
Release();
_ptr = sp._ptr; // 资源地址赋值
_pcount = sp._pcount; // 引用计数赋值
_pmtx->lock();
(*_pcount)++;// 引用计数++
_pmtx->unlock();
}
}
shared_ptr& operator=(shared_ptr& sp)
{
if (this != &sp)
{
delete _ptr;
_ptr = sp._ptr;
sp._ptr = nullptr;
}
return *this;
}
int use_count()
{
return *_pcount;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
int* _pcount;
mutex* _pmtx;
};
void test_shared_ptr()
{
int n = 10000;
shared_ptr sp1(new int(1));
thread t1([&]()
{
for (int i = 0; i < n; i++)
{
shared_ptr sp2(sp1);
}
});
thread t2([&]()
{
for (int i = 0; i < n; i++)
{
shared_ptr sp3(sp1);
}
});
t1.join();
t2.join();
cout << sp1.use_count() << endl;
}
struct Date
{
int _year = 0;
int _month = 0;
int _day = 0;
};
void test_shared_ptr()
{
int n = 100000;
shared_ptr sp1(new Date);
thread t1([&]()
{
for (int i = 0; i < n; i++)
{
shared_ptr sp2(sp1);
sp2->_year++;
sp2->_month++;
sp2->_day++;
}
});
thread t2([&]()
{
for (int i = 0; i < n; i++)
{
shared_ptr sp3(sp1);
sp3->_year++;
sp3->_month++;
sp3->_day++;
}
});
t1.join();
t2.join();
cout << sp1.use_count() << endl;
cout << sp1->_year << endl;
cout << sp1->_month << endl;
cout << sp1->_day << endl;
}
管理的资源不是线程安全的,但是引用计数是线程安全的。
关于之前的循环引用的写法是:
struct ListNode
{
int val;
ListNode* _next;
ListNode* _prev;
};
void test_shared_ptr2()
{
ListNode* n1 = new ListNode;
ListNode* n2 = new ListNode;
n1->_next = n2;
n2->_prev = n1;
delete n1;
delete n2;
}
如果我们想用智能指针进行进行修改:
struct ListNode
{
int val;
shared_ptr _next;
shared_ptr _prev;
//检测结点是否释放
~ListNode()
{
cout << "delete" << endl;
}
};
void test_shared_ptr2()
{
shared_ptr n1 = new(ListNode);
shared_ptr n2 = new(ListNode);
n1->_next = n2;
n2->_prev = n1;
}
会有循环引用的问题,将造成内存泄漏
当n1和n2造成循环引用的条件的时候
当资源对应的引用计数减为0时对应的资源才会被释放,因此资源1的释放取决于资源2当中的prev成员,而资源2的释放取决于资源1当中的next成员。
而资源1当中的next成员的释放又取决于资源1,资源2当中的prev成员的释放又取决于资源2,于是这就变成了一个死循环,最终导致资源无法释放。
//可以指向/访问资源,不参与资源管理,不增加引用计数
weak_ptr只是shared_ptr的小弟,它根本不支持RAII,他只是辅助shared_ptr
struct ListNode
{
int val;
//可以指向/访问资源,不参与资源管理,不增加引用计数
weak_ptr _next;
weak_ptr _prev;
//检测结点是否释放
~ListNode()
{
cout << "delete" << endl;
}
};
void test_shared_ptr2()
{
std::shared_ptr n1(new ListNode);
std::shared_ptr n2(new ListNode);
n1->_next = n2;
n2->_prev = n1;
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
}
template
class weak_ptr
{
public:
// 保存资源
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
{}
weak_ptr& operator=(const shared_ptr& sp)
{
_ptr = sp.get();
return *this;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
定制删除器的意思是,我们可以传入一个定制删除资源的方法,它就会按照我们想要删除的方式进行删除。
我们可以使用仿函数和lambda表达式来解决
template
struct DeleteArray
{
void operator()(const T* ptr)
{
delete[] ptr;
cout << "delete []" << ptr << endl;
}
};
int main()
{
//仿函数
std::shared_ptr sp1(new int[10], DeleteArray());
std::shared_ptr sp2(new string[10], DeleteArray());
//lambda表达式
std::shared_ptr sp3(new string[10], [](string* ptr) {delete[] ptr; });
//文件类型的管理
std::shared_ptr sp4(fopen("test.cc", "r"), [](FILE* ptr){ fclose(ptr); });
return 0;
}