C++进阶_智能指针
1.为什么用智能指针
(1)内存泄漏问题:malloc出来的空间,没有经过free,会导致内存泄漏
(2)异常安全问题:在malloc和free之间存在抛异常,那么还会导致内存泄漏
#include<iostream>
#include<cstdlib>
#include<vector>
using namespace std;
int main()
{
try
{
int* tmp = (int*)malloc(sizeof(int) * 10);
vector<int> v(10, 5);
v.at(10) = 20;//将下标为10的位置的数置为20
cout << "free" << endl;
free(tmp);
}
catch(const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
2.智能指针的使用及其原理
RALL
RALL是一种利用对象生命周期来控制程序资源(内存、文件句柄、网络连接、互斥量等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构时释放资源。实际上把管理一份资源的责任托管给了一个对象。
好处:
(1)不需要显式的释放资源
(2)对象所需要的资源在其生命周期内始终保持有效
#include<iostream>
#include<cstdlib>
#include<vector>
using namespace std;
template<class T>
class SmartPtr
{
private:
T* _ptr;
public:
SmartPtr(T* ptr = nullptr) :_ptr(ptr)
{
cout << "SmartPtr(T)->" << _ptr << endl;
}
~SmartPtr()
{
if (_ptr)
{
cout << "~SmartPtr()->" << _ptr << endl;
delete _ptr;
}
}
};
int main()
{
try
{
int* tmp = (int*)malloc(sizeof(int) * 10);
SmartPtr<int> sp(tmp);
vector<int> v(10, 5);
v.at(10) = 20;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
智能指针的原理
- RALL特性
- 重载operator* 和opeator->,具有像指针一样的行为
template<class T>
class SmartPtr
{
private:
T* _ptr;
public:
SmartPtr(T* ptr=nullptr):_ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
{
delete _ptr;
}
}
T& operator*()//重载*是为了取值,所以用引用
{
return *_ptr;
}
T* operator->()//重载->是为了取地址,所以用指针
{
return _ptr;
}
};
struct Date
{
int _y;
int _m;
int _d;
};
int main()
{
int* tmp = new int();
SmartPtr<int> sp(tmp);
*sp = 10;
cout << "*sp:" << *sp << endl;
cout << "*tmp:" << *tmp << endl;
Date* date = new Date();
SmartPtr<Date> sd(date);
sd->_y = 2020;
sd->_m = 5;
sd->_d = 7;
cout << date->_y << " " << date->_m << " " << date->_d << endl;
return 0;
}
std::auto_ptr
头文件:#inlcude
问题:auto_ptr对象拷贝赋值后,前面的对象悬空(为了解决一块空间被多个对象使用所造成的程序崩溃)
#include<iostream>
#include<memory>
using namespace std;
class Date
{
public:
int _y;
int _m;
int _d;
public:
Date()
{
cout << " Date()" << endl;
}
~Date()
{
cout << "~Date()" << endl;
}
};
int main()
{
Date* date = new Date();
auto_ptr<Date> ap(date);
ap->_y = 2020;//auto_ptr对象ap拷贝或者赋值后,悬空
auto_ptr<Date> ad(ap);//ap为空
//ad->_y = 2019;
return 0;
}
auto_ptr实现原理:管理权转移
#include<iostream>
using namespace std;
namespace mystd
{
template<class T>
class auto_ptr
{
private:
T* _ptr;
public:
auto_ptr(T* ptr=nullptr):_ptr(ptr)
{}
~auto_ptr()
{
if (_ptr)
{
delete _ptr;
}
}
auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap)
{
if (_ptr)
{
delete _ptr;
}
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
};
}
class Date
{
public:
int _y;
};
int main()
{
mystd::auto_ptr<Date> myap(new Date());
myap->_y = 2020;
cout << myap->_y << endl;
mystd::auto_ptr<Date> cp(myap);
//cp->_y = 2020;
return 0;
}
std::unique_ptr
std::unique_ptr:防拷贝,防赋值,避免了auto_ptr问题的出现
#include<iostream>
#include<memory>
using namespace std;
int main()
{
int* tmp = new int();
unique_ptr<int> uq(tmp);
*uq = 2;
cout << *uq << " " << *tmp << endl;
//unique_ptr<int> cp(uq);
return 0;
}
unique_ptr实现原理:防拷贝防赋值
#include<iostream>
using namespace std;
namespace mystd
{
template<class T>
class unique_ptr
{
private:
T* _ptr;
private:
unique_ptr(unique_ptr<T> const&);//拷贝
unique_ptr<T>& operator=(unique_ptr<T> const&);//赋值
public:
unique_ptr(T* ptr=nullptr):_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
};
}
int main()
{
int* tmp = new int();
mystd::unique_ptr<int> uq(tmp);
*uq = 3;
cout << *tmp << endl;
//mystd::unique_ptr<int> cp(uq);//防拷贝(私有成员,无法访问)
//mystd::unique_ptr<int> cp = uq;//防赋值(私有成员,无法访问)
return 0;
}
std::shared_ptr
std::shared_ptr:支持拷贝
#include<iostream>
#include<memory>
using namespace std;
int main()
{
int* tmp = new int();
shared_ptr<int> sp(tmp);
shared_ptr<int> cp(sp);
*sp = 10;
cout << *tmp << " " << *sp << endl;
*cp = 20;
cout << *tmp << " " << *sp << endl;//拷贝
return 0;
}
shared_ptr实现原理
shared_ptr:通过引用计数的方式来实现多个shared_ptr对象之间的资源共享
- shared_ptr在内部为每个资源都维护着一份计数,用来记录该资源被几个对象共享
- 在对象被销毁时(调用析构函数),即不再使用该资源,对象的引用计数-1
- 当引用计数为0时,说明自己是最后一个使用该资源的对象,必须释放该资源
- 如果不是0,则说明该资源被其他对象所使用,不能释放该资源,否则其他对象会成为野指针
shared_ptr的循环引用
#include<iostream>
#include<memory>
using namespace std;
class Node
{
public:
int _data;
shared_ptr<Node> _prev;//前驱
shared_ptr<Node> _next;//后继
public:
Node()
{
cout << "Node()" << endl;
}
~Node()
{
cout << "~Node()" << endl;
}
};
int main()
{
shared_ptr<Node> node1(new Node());
shared_ptr<Node> node2(new Node());
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0;
}
说明:
(1)node1与node2两个智能指针对象指向两个结点,引用计数分别为1
(2)node1的next指向node2,node2的prev指向node1,引用计数分别变为2
(3)node1和node2分别管理两个结点,node1的next管理着node2,所以必须得等到node2释放,node1才可以释放
(4)node2的prev管理着node1,所以必须得等到node1释放,node2才可以释放
(5)最终谁也不能释放,就成了循环引用
解决方案:将结点中的_prev和_next改成weak_ptr即可。原理是node1->_next = node2;和node2->_prev = node1时weak_ptr的_next和_prev不会增加node1和node2的引用计数。
#include<iostream>
#include<memory>
using namespace std;
class Node
{
public:
int _data;
weak_ptr<Node> _prev;//前驱
weak_ptr<Node> _next;//后继
public:
Node()
{
cout << "Node()" << endl;
}
~Node()
{
cout << "~Node()" << endl;
}
};
int main()
{
shared_ptr<Node> node1(new Node());
shared_ptr<Node> node2(new Node());
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0;
}