目录
一、C++中裸指针的不安全性
1、作为类的成员
2、异常处理
二、智能指针的引入
1、unique_ptr
2、shared_ptr
3、weak_ptr
二、智能指针的简易实现
1、unique_ptr的实现
2、shared_ptr的实现
?总结
????????C++的指针是C++中非常重要的概念,它在堆上为对象分配内存,但是裸指针(普通指针)使用很不安全,处理不当容易变成野指针(指向的内存已释放),造成内存泄露。C++的指针在开发场景中可能经常被多次复制、赋值、移动,例如函数传参、并行场景处理共享对象等。
一、C++中裸指针的不安全性
C++中的裸指针在使用时容易出现不安全的场景,这里简单列举几个常见情景。
1、作为类的成员
假如一个C++类中含有裸指针作为类成员,那么其析构函数和拷贝构造函数必须被重写,必要时也要重载赋值操作符,这样才能保证类A在使用中的安全。
class A{
public:
A():p(nullptr){}
A(const int *_p):p(new int(*_p)){}// 拷贝构造函数
~A(){if(p)delete p;} // 析构函数
// 重载=操作符
A& operator=(const A &_a){
if(this==&_a) return *this;
if(p)delete p;// 将原先的内存释放掉
this->p=_a.p;// 修改p的执行
return *this;
}
private:
int * p;
}
2、异常处理
void process()
{
A* a= new A();
a->do_something(); // 可能会发生异常
delete a;
}
? ? ? ? 上图是一种很常见的使用形式,在正常流程下会正常执行。但是如果a->do_something()发生了异常,那么delete a 将不会被执行。此时就会发生内存泄漏。我们当然可以使用try...catch捕捉异常,在catch里面执行delete,但是这样代码上并不美观,也容易漏写。
二、智能指针的引入
? ? ? 为了更加安全地使用动态内存,C++引入了智能指针的概念。智能指针(pointer-like classes,像指针的类)。智能指针在用法上基本类似普通的C++指针,其重要区别是它负责自动释放所指向的对象。
? ? ? C++98提供了auto_ptr;C++11舍弃了C++98的auto_ptr,在头文件<memory>提供了3个新的智能指针类型,这些智能指针都是在裸指针的基础上封装而来的,对比如下:
| 对象所有权 | 执行效率 | 应用频率 | 安全性 | 特点 | 裸指针 | — | 最高 | 最高 | 较低 | 手动delete、复制、拷贝、处理异常等 | unique_ptr<T> | 专属所有权 | 较高 | 较高 | 较高 | 不支持复制和赋值;支持移动; | shared_ptr<T> | 共享所有权 | 较低 | 较低 | 较高 | 支持复制、赋值和移动;循环引用时出错 | weak_ptr<T> | 临时所有权 | 较低 | 最低 | 较高 | 支持复制、赋值和移动;弥补循环引用 |
注意:对象所有权是很多编程语言都会涉及的概念,在C++中可以理解成对对象的内存资源的操作权。
? ? ? ? 通过上表我们可以发现3种智能指针各有不同的特点,应用于不同的场景,裸指针的使用则需要考虑多种情况,不够安全,但在实际开发中智能指针不能不加思考地全部使用智能指针处理,常见的理由如下:
1、智能指针具有高传染性,在项目中使用智能指针很可能要替换很多裸指针,风险很大。
2、在复杂场景中,智能指针出现相互引用时则危险性很高。
3、智能指针的开销大概率会比裸指针大。
? ? ? 我们假设存在一个类A,尝试使用以下几个智能指针去展示其用法:
class A{
public:
A(){cout << "A default constructor !"<<endl;}
void print(){cout << "call A.print() !"<<endl;}
~A(){cout << "A deconstructor !"<<endl;}
};
1、unique_ptr<T>
? ? ? ? unique_ptr是一种具有专属所有权的智能指针。unique_ptr管理的内存只能被一个对象持有,该指针不支持复制和赋值,只支持移动。其内存占用和执行性能和裸指针接近。? unique_ptr作为类成员时,不需要在析构函数中delete;unique_ptr在执行代码抛出异常时,离开作用域也能自行释放内存。
{
unique_ptr<A> pA(new A());//pA指向A,拥有A的内存资源的所有权
pA->print();// 访问A的方法
//(一)不支持拷贝
//unique_ptr<A> pA1(pA);//编译报错,不支持拷贝
//(二)不支持赋值
unique_ptr<A> pA2(new A());//pA指向A,拥有A的内存资源的所有权
//pA2=pA; // 编译报错,不支持赋值
//(三)支持移动
unique_ptr<A> pA3 = std::move(pA);//pA将所有权移交pA3,自己失去所有权
if(pA == NULL) cout<<"pA = NULL\n";//
}
?输出结果如下:
2、shared_ptr<T>
? ? ? ?shared_ptr是一种具有共享所有权的智能指针。shared_ptr管理的内存可以被多个对象持有,内部使用引用计数来管理内存;该指针支持复制和赋值和移动。由于shared_pt需要额外维护一个原子级别的引用计数,其内存占用和执行效率都远远不如裸指针。该指针适合用于共享权不明的场景,比如并发场景的多线程。shared_ptr可以被多个线程同时读,同时写时需要加锁。
{
shared_ptr<A> pA(new A());//pA指向A,拥有A的内存资源的所有权
pA->print();// 访问A的方法
cout<<"current Ref Count (" << pA.use_count() << ") "<<endl;
//(一)支持拷贝
shared_ptr<A> pA1(pA);//支持拷贝
cout<<"current Ref Count (" << pA.use_count() << ") "<<endl;
//(二)支持赋值
shared_ptr<A> pA2(new A());//pA指向A,拥有A的内存资源的所有权
pA2=pA; //支持赋值
cout<<"current Ref Count (" << pA.use_count() << ") "<<endl;
//(三)支持移动
shared_ptr<A> pA3 = std::move(pA);//pA将所有权移交pA3,自己失去所有权
if(pA == NULL) cout<<"pA = NULL\n";
cout<<"current Ref Count (" << pA.use_count() << ") "<<endl;
}
输出结果如下:
?可以看到,pA在移动前引用计数在一直增加,移动后,引用计数变为0,因为其引用计数被释放。
3、weak_ptr<T>
? ? ? ?weak_ptr是一种具有临时所有权的智能指针。当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用它来跟踪该对象。需要获得临时所有权时,则将其转换为shared_ptr,此时如果原来的shared_ptr被销毁,则该对象的生命期将被延长至这个临时的shared_ptr同样被销毁为止。
????????shared_ptr的双向引用问题:
#include<memory>
class B;
class A{
shared_ptr<B> b;
};
class B{
shared_ptr<A> a;
};
auto pa = make_shared<A>();
auto pb = make_shared<B>();
pa->b = pb;
pb->a = pa;
????????pa和pb存在着循环引用,根据shared_ptr引用计数的原理,pa和pb都无法被正常的释放。 我们可以将其中的一个?shared_ptr修改为weak_ptr.
? ? ? ?weak_ptr可以认为是shared_ptr的补充,它可以解决shared_ptr双向引用的问题,经常需要和 shared_ptr 一起使用,例如用于类的继承场景:父类持有指向子类的shared_ptr,子类持有指向父类的weak_ptr。
二、智能指针的简易实现
1、unique_ptr的实现
unique_ptr成员变量只有一个裸指针mPointer
重写析构函数、拷贝构造函数、移动构造函数
然后需要重载运算符&、->、=
template<class T>
class unique_p{
public:
// 构造函数
unique_p(): mPointer(nullptr){}
unique_p(T* p): mPointer(p){
cout << "create smart pointer at " << static_cast<const void *>(p) <<endl;
}
// 拷贝构造函数
unique_p(const unique_p &_p):mPointer(_p.mPointer){
cout <<"Copy smart pointer at "<<static_cast<const void*>(_p.mPointer)<<endl;
}
// 移动构造函数
unique_p(unique_p&& _p) {
mPointer = _p.mPointer;
_p.mPointer = NULL;
}
// 移动赋值函数
unique_p& operator=(unique_p&& _p) {
mPointer = _p.mPointer;
_p.mPointer = NULL;
return *this;
}
// 重载*和->操作符
unique_p& operator*() const {return *this;}
T* operator->() const {return this->mPointer;}
// 重载=操作符
unique_p& operator =(const unique_p &_p) {
if(this==&_p) return *this;
this->reset();// 将原先的内存释放掉
this->mPointer=_p.mPointer;
return *this;
}
// 析构函数
~unique_p(){
this->reset(); // 实现内存资源自动销毁机制
}
private:
void reset() {
if(mPointer) {
cout << "real release smart pointer at " << static_cast<const void *>(mPointer) <<endl;
delete mPointer;
}
}
private:
T* mPointer;// C++实际指针
};
测试代码如下:
// 创建共享指针1
unique_p<A> pAOuter(new A());
// 移动
unique_p<A> sptr2(std::move(pAOuter)); // 调用移动构造函数
cout << "------------------------------------------------" <<endl;
{
unique_p<A> sptr3;
sptr3 = std::move(sptr2); // 调用移动赋值函数
}
输出:
2、shared_ptr的实现
shared_ptr内部具有引用计数,所以其成员变量除了裸指针mPointer,还需要引用计数mRefCount。
重写析构函数、拷贝构造函数、移动构造函数
然后需要重载运算符&、->、=
template<class T>
class shared_p{
public:
// 构造函数
shared_p(): mPointer(nullptr),mRefCount(0){}
shared_p(T* p): mPointer(p),mRefCount(0){
cout << "create smart pointer at " << static_cast<const void *>(p) <<endl;
if(mPointer) mRefCount++;// 引用计数+1
}
// 拷贝构造函数
shared_p(const shared_p &_p):mPointer(_p.mPointer),mRefCount(_p.mRefCount){
cout <<"Copy smart pointer at "<<static_cast<const void*>(_p.mPointer)<<endl;
mRefCount++;// 引用计数+1
}
// 移动构造函数
shared_p(shared_p&& _p) {
mPointer = _p.mPointer;
mRefCount=_p.mRefCount;
_p.mPointer = NULL;
_p.mRefCount=0;
}
// 移动赋值函数
shared_p& operator=(shared_p&& _p) {
mPointer = _p.mPointer;
mRefCount=_p.mRefCount;
_p.mPointer = NULL;
_p.mRefCount=0;
return *this;
}
// 重载*和->操作符
shared_p& operator*() const {return *this;}
T* operator->() const {return this->mPointer;}
// 重载=操作符
shared_p& operator =(const shared_p &_p) {
if(this==&_p) return *this;
this->reset();// 将原先的内存释放掉
this->mPointer=_p.mPointer;
this->mRefCount=_p.mRefCount;
mRefCount++;// 引用计数+1
return *this;
}
// 析构函数
~shared_p(){
cout << "release smart pointer at " << static_cast<const void *>(mPointer) <<endl;
this->reset(); // 实现内存资源自动销毁机制
}
int count() const {return mRefCount ? mRefCount:0;}
private:
void reset() {
if(mRefCount) {
mRefCount--;
if(mRefCount == 0) {
cout << "real release smart pointer at " << static_cast<const void *>(mPointer) <<endl;
delete mPointer;
}
}
}
private:
T* mPointer;// C++实际指针
int mRefCount;// 引用计数
};
测试代码如下:
// 创建共享指针1
shared_p<A> pAOuter(new A());
cout << "current Ref Count (" << pAOuter.count() << ") outer 1."<<endl;
cout << "------------------------------------------------" <<endl;
{ // 复制
shared_p<A> pA(pAOuter);
std::cout << "current Ref Count (" << pA.count() << ") inner."<<endl;
}
cout << "------------------------------------------------" <<endl;
cout << "current Ref Count (" << pAOuter.count() << ") outer 2."<<endl;
// 赋值
shared_p<A> spOuter2(new A());
pAOuter = spOuter2;// 1处new出来的SomeClass将会被自动释放
cout << "current Ref Count (" << pAOuter.count() << ") outer 2."<<endl;
// 移动
cout << "------------------------------------------------" <<endl;
shared_p<A> sptr2(std::move(pAOuter)); // 调用移动构造函数
shared_p<A> sptr3;
sptr3 = std::move(sptr2); // 调用移动赋值函数
cout << "current Ref Count (" << pAOuter.count() << ") outer 2."<<endl;
输出:
?总结
? 智能指针本质是一个C++类模板,封装了C++的普通类指针,覆写了析构函数、拷贝构造函数、移动构造函数,重载了*、->、=操作符函数。
如果本文对您有帮助,欢迎点赞支持!
|