智能指针基础知识
裸指针
#include<iostream>
using namespace std;
int main()
{
int *p = new int(10); //动态分配内存并初始化为10(堆空间)
*p = 20;
delete p; //释放内存
return 0;
}
裸指针可能引发的问题:用户忘记手动释放、程序在 delete 之前某个地方条件成立提前退出等
那我们在使用指针的过程中如何做到无论我们程序发生异常亦或其他情况都能够稳定的释放内存呢?
这里我们引出了 智能指针
智能指针
智能指针其实就是对裸指针的一种封装,那么智能指针是如何做到自动释放内存的呢?一个简单的智能指针封装示例:
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T *ptr = nullptr)
:mptr(ptr) {}
~CSmartPtr() { delete mptr; }
T& operator*() { return *mptr; }
private:
T *mptr;
};
int main()
{
CSmartPtr<int> ptr1(new int);
*ptr1 = 20;
return 0;
}
从示例代码我们可以看出,构造了一个智能指针类将裸指针包装成私有成员变量使用,并定义了成员方法对裸指针进行解引用操作,main函数中定义了一个智能指针,然后进行解引用操作,当main函数结束,系统会自动调用智能指针中类的析构方法进行堆内存释放,这样就做到了稳定释放内存的效果,其原理是利用了 栈上对象出作用域自动析构 这个特征,来代替 堆上内存的手动释放
智能指针的浅拷贝问题
#include<iostream>
#include<memory>
using namespace std;
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T *ptr = nullptr)
:mptr(ptr){}
~CSmartPtr() { delete mptr; }
T& operator*() { return *mptr; }
private:
T *mptr;
};
int main()
{
CSmartPtr<int> p1(new int);
CSmartPtr<int> p2(p1);
*p1 = 10;
cout << *p2 << endl;
return 0;
}
我们可以看到,当我们调用拷贝构造构造一个对象 p2 ,再给 p1 赋值最后输出 p2,出现了两次释放内存的报错,这是因为 调用默认拷贝构造函数构造的新对象与原对象共享一块内存,造成的浅拷贝问题,调用 p1 的析构释放内存的时候,原内存已经被释放了;如何解决这样的浅拷贝问题呢?
#include<iostream>
#include<memory>
using namespace std;
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T *ptr = nullptr)
:mptr(ptr){}
CSmartPtr(const CSmartPtr<T>& src)
{
mptr = new T(*src.mptr);
}
~CSmartPtr() { delete mptr; }
T& operator*() { return *mptr; }
private:
T *mptr;
};
int main()
{
CSmartPtr<int> p1(new int);
CSmartPtr<int> p2(p1);
*p1 = 10;
cout << *p2 << endl;
return 0;
}
这里我们对加上了一段拷贝构造函数,尝试利用一般的解决浅拷贝问题的方法优化
输出结果为 0 ,并不与 p1 的值相同,这说明 p1、 p2 都有各自的内存空间,并不指向同一块内存;
从用户需求来看,拷贝构造 p1 之后,新对象 p2 内存中的值应该与 p1 相同(两个不同地址的裸指针可以指向同一块内存),显然这样的避免浅拷贝的方法是不可行的
不带引用计数的智能指针
不带引用计数的智能指针:一个指针只能管理一块资源,不同的指针无法管理同一块资源
auto_ptr(已弃用)
#include<iostream>
#include<memory>
using namespace std;
int main()
{
auto_ptr<int> p1;
auto_ptr<int> p2(p1);
return 0;
}
auto_ptr 解决浅拷贝的方法是将原指针的内存空间转移到新拷贝的指针空间上,会导致原指针无法使用,但用户无法感知这样的对象资源的转移
scoped_ptr
scoped_ptr 移除拷贝构造和赋值运算符重载,禁止了指针的拷贝,从而解决了浅拷贝问题
unique_ptr
#include<iostream>
#include<memory>
using namespace std;
int main()
{
unique_ptr<int> p1(new int);
*p1 = 10;
unique_ptr<int> p2(move(p1));
cout << *p2 << endl;
return 0;
}
unique_ptr 同样删除了左值引用的拷贝构造和赋值运算符重载函数,与 scoped_ptr 不同的是其添加了右值引用的拷贝构造和赋值运算符重载函数,通过移动语义调用移动构造,做到了显式的资源转移
带引用计数的智能指针
带引用计数的智能指针:多个智能指针可以管理同一个资源,并给每一个对象匹配一个引用计数
当智能指针管理资源的时候,引用计数加一;不使用资源的时候,引用计数减一,当智能指针引用计数为零时,析构要释放内存,一个带引用计数的智能指针的实现
#include<iostream>
#include<memory>
using namespace std;
template<typename T>
class RefCount
{
public:
RefCount(T *ptr = nullptr)
:mptr(ptr)
{
if(mptr)
m_count = 1;
}
void addRef(){ m_count++; }
int delRef() { return --m_count;}
private:
T *mptr;
int m_count;
};
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T *ptr = nullptr)
:mptr(ptr)
{
refCount = new RefCount<T>(mptr);
}
~CSmartPtr()
{
if(refCount->delRef() == 0)
{
delete mptr;
mptr = nullptr;
}
}
CSmartPtr(const CSmartPtr<T>& src)
:mptr(src.mptr),refCount(src.refCount)
{
if(mptr)
refCount->addRef();
}
CSmartPtr<T>& operator=(const CSmartPtr<T>& src)
{
if(this == &src)
return *this;
if(0 == refCount->delRef())
{
delete mptr;
mptr = nullptr;
}
mptr = src.mptr;
refCount = src.refCount;
if(mptr)
refCount->addRef();
return *this;
}
T* operator->() { return mptr; }
T& operator*() { return *mptr; }
private:
T *mptr;
RefCount<T> *refCount;
};
int main()
{
CSmartPtr<int> p1(new int);
CSmartPtr<int> p2(p1);
*p1 = 10;
cout << "p2 = " << *p2 << endl;
return 0;
}
shared_ptr 和 weak_ptr
shared_ptr :强智能指针,可以改变引用计数
weak_ptr:弱智能指针,不可改变引用计数
shared_ptr 的交叉引用问题
#include<iostream>
#include<memory>
using namespace std;
class B;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
shared_ptr<B> _ptrb;
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
shared_ptr<A> _ptra;
};
int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
cout << pa.use_count() << endl;
cout << pb.use_count() << endl;
return 0;
}
int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->_ptrb = pb;
pb->_ptra = pa;
cout << pa.use_count() << endl;
cout << pb.use_count() << endl;
return 0;
}
可以看到,当增加 pa->_ptrb = pb; pb->_ptra = pa;时,输出结果没有调用析构函数,会造成内存泄露
如何解决强智能指针的交叉引用问题?
定义对象用强智能指针,引用对象用弱智能指针
引用对象时需要二者一起使用,参考如下代码 :
#include<iostream>
#include<memory>
using namespace std;
class B;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void testA() { cout << "this is a good way" << endl;}
weak_ptr<B> _ptrb;
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
void func()
{
shared_ptr<A> ps = _ptra.lock(); //方法提升
if(ps)
{
ps->testA();
}
}
weak_ptr<A> _ptra;
};
int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->_ptrb = pb;
pb->_ptra = pa;
cout << pa.use_count() << endl;
cout << pb.use_count() << endl;
return 0;
}
运行:
多线程访问共享对象的线程安全问题
#include<iostream>
#include<memory>
#include<thread>
using namespace std;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void func() { cout << "this is a good way" << endl; }
};
void handler(A* p)
{
std::this_thread::sleep_for(std::chrono::seconds(20));
p->func();
}
int main()
{
A *p = new A();
thread t(handler, p);
delete p;
t.join(); //等待子线程结束
return 0;
}
上述代码提供了一个场景,当子线程 t 调用 p对象中的 func 函数之前会睡眠 20 秒,但此时主线程中
对象p资源已经被释放,显然是不合理的;handler 线程调用func函数在安全的情况下,应该先侦测A对象是否存活,这里我们使用强智能指针和弱智能指针优化:
#include<iostream>
#include<memory>
#include<thread>
using namespace std;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void func() { cout << "this is a good way" << endl; }
};
void handler(weak_ptr<A> pw)
{
std::this_thread::sleep_for(std::chrono::seconds(2));
shared_ptr<A> ps = pw.lock();
if(ps)
{
ps->func();
}
else
{
cout << "ps is null" << endl;
}
}
int main()
{
{
shared_ptr<A> p(new A());
thread t(handler, weak_ptr<A> (p));
t.detach();
}
std::this_thread::sleep_for(std::chrono::seconds(10));
return 0;
}
可以看到,此时主函数中 A 对象出作用域析构时,fun睡眠 2 秒,未被调用,尝试让线程 t 在 A 未被析构前调用方法,加上睡眠函数:
int main()
{
{
shared_ptr<A> p(new A());
thread t(handler, weak_ptr<A> (p));
t.detach();
std::this_thread::sleep_for(std::chrono::seconds(5));
}
std::this_thread::sleep_for(std::chrono::seconds(10));
return 0;
}