目录
裸指针可能发生的问题:
1.忘记释放资源,导致资源泄露(常发生内存泄漏问题);
2.写了释放内存部分,但由于程序逻辑满足条件可能在释放内存之前就return结束运行;
3.多次释放同一块内存,产生野指针;
2.由于程序执行出现异常,释放内存的部分没执行到;
智能指针:
自己能做到资源的自动释放:
template<typename T> class CSmartPtr { public: CSmartPtr(T *ptr = nullptr) :mptr(ptr) {} ~CSmartPtr() { delete mptr; } private: T *mptr; }; int main() { CSmartPtr<int> ptr(new int); /*其它的代码...*/ /*由于ptr是栈上的智能指针对象,不管是函数正常执行完,还是运行过程中出现 异常,栈上的对象都会自动调用析构函数,在析构函数中进行了delete 操作,保证释放资源*/ return 0; }
1)智能指针体现在把裸指针进行了一次面向对象的封装,在构造函数中初始化资源地址,在析构函数中负责释放资源
2)利用栈上的对象出作用域自动析构这个特点,在智能指针的析构函数中保证释放资源智能指针一般都是定义在栈上的
RAII技术
确保对象被创建时获取资源,对象被销毁时自动释放资源,从而避免资源泄露的发生。通常是在对象的构造函数中获取资源,并在析构函数中释放资源来实现。
智能指针需要解决的两件事情:
- 怎么解决指针的浅拷贝问题
- 多个智能指针指向同一个资源的时候,怎么保证资源只释放一次,而不是每个智能指针都释放一次,造成代码运行不可预期的严重后果
不带引用计数的智能指针
auto_ptr
int main() { auto_ptr<int> p1(new int); /* 经过拷贝构造,p2指向了new int资源, p1现在为nullptr了,如果使用p1,相当于访问空指针了,很危险 */ auto_ptr<int> p2 = p1; *p1 = 10; return 0; }
auto_ptr智能指针不带引用计数,那么它处理浅拷贝的问题,是直接把前面的auto_ptr都置为nullptr,只让最后一个auto_ptr持有资源。
scoped_ptr
scoped_ptr ( const scoped ptr<T>&) = delete; scoped_ptr<T>& operator=(const scoped ptr<T>&) = delete;
优化了拷贝构造函数和operator=赋值函数,因此从根本上杜绝了智能指针浅拷贝的发生,所以scoped_ptr也是不能用在容器当中的,如果容器互相进行拷贝或者赋值,就会引起scoped_ptr对象的拷贝构造和赋值,这是不允许的,代码会提示编译错误。
unique_ptr
让一个智能指针管理资源
unique_ptr有一点和scoped_ptr做的一样,就是去掉了拷贝构造函数和operator=赋值重载函数,禁止用户对unique_ptr进行显示的拷贝构造和赋值,防止智能指针浅拷贝问题的发生。
unique_ptr ( const scoped ptr<T>&) = delete;
unique_ptr<T>& operator=(const scoped ptr<T>&) = delete;
但是unique_ptr提供了带右值引用参数的拷贝构造和赋值,unique_ptr智能指针可以通过右值引用进行拷贝构造和赋值操作
unique_ptr (unique_ptr<T> &&src);
unique_ptr<T>& operator=(unique_ptr<T> &&src);
unique_ptr<int> ptr(new int);
unique_ptr<int> ptr2 = std::move(ptr); // 使用了右值引用的拷贝构造
ptr2 = std::move(ptr); // 使用了右值引用的operator=赋值重载函数
带引用计数的智能指针
带引用计数:多个智能指针可以管理同一个资源,每一个智能指针都会给资源的引用计数加1,当一个智能指针析构时,同样会使资源的引用计数减1,这样最后一个智能指针把资源的引用计数从1减到0时,就说明该资源可以释放了,由最后一个智能指针的析构函数来处理资源的释放问题。
要对资源的引用个数进行计数,那么大家知道,对于整数的++或者- -操作,它并不是线程安全的操作,因此shared_ptr和weak_ptr底层的引用计数已经通过CAS操作,保证了引用计数加减的原子特性,因此shared_ptr和weak_ptr本身就是线程安全的带引用计数的智能指针。(atomic_int CAS)
shared_ptr
强智能指针 可以改变资源的引用计数
private:
/*
下面这两个是shared_ptr的成员变量,_Ptr是指向内存资源的指针,_Rep是
指向new出来的计数器对象的指针,该计数器对象包含了资源的一个引用计数器count
*/
element_type * _Ptr{nullptr};
_Ref_count_base * _Rep{nullptr};
weak_ptr
弱智能指针 不会改变资源的引用计数
智能指针的交叉引用(循环引用)问题
#include <iostream>
#include <memory>
using namespace std;
class B; // 前置声明类B
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
shared_ptr<B> _ptrb; // 指向B对象的智能指针
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
shared_ptr<A> _ptra; // 指向A对象的智能指针
};
int main()
{
shared_ptr<A> ptra(new A());// ptra指向A对象,A的引用计数为1
shared_ptr<B> ptrb(new B());// ptrb指向B对象,B的引用计数为1
ptra->_ptrb = ptrb;// A对象的成员变量_ptrb也指向B对象,B的引用计数为2
ptrb->_ptra = ptra;// B对象的成员变量_ptra也指向A对象,A的引用计数为2
cout << ptra.use_count() << endl; // 打印A的引用计数结果:2
cout << ptrb.use_count() << endl; // 打印B的引用计数结果:2
/*
出main函数作用域,ptra和ptrb两个局部对象析构,分别给A对象和
B对象的引用计数从2减到1,达不到释放A和B的条件(释放的条件是
A和B的引用计数为0),因此造成两个new出来的A和B对象无法释放,
导致内存泄露,这个问题就是“强智能指针的交叉引用(循环引用)问题”
*/
return 0;
}
//代码打印结果:
A()
B()
2
2
定义对象时,用强智能指针shared_ptr,在其它地方引用对象时,使用弱智能指针weak_ptr。
弱智能指针weak_ptr区别于shared_ptr之处在于:
1.weak_ptr不会改变资源的引用计数,只是一个观察者的角色,通过观察shared_ptr来判定资源是否存在
2.weak_ptr持有的引用计数,不是资源的引用计数,而是同一个资源的观察者的计数
3.weak_ptr没有提供常用的指针操作,无法直接访问资源,需要先通过lock方法提升为shared_ptr强智能指针,才能访问资源。
上述代码修改:
#include <iostream>
#include <memory>
using namespace std;
class B; // 前置声明类B
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
weak_ptr<B> _ptrb; // 指向B对象的弱智能指针。引用对象时,用弱智能指针
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
weak_ptr<A> _ptra; // 指向A对象的弱智能指针。引用对象时,用弱智能指针
};
int main()
{
// 定义对象时,用强智能指针
shared_ptr<A> ptra(new A());// ptra指向A对象,A的引用计数为1
shared_ptr<B> ptrb(new B());// ptrb指向B对象,B的引用计数为1
// A对象的成员变量_ptrb也指向B对象,B的引用计数为1,因为是弱智能指针,引用计数没有改变
ptra->_ptrb = ptrb;
// B对象的成员变量_ptra也指向A对象,A的引用计数为1,因为是弱智能指针,引用计数没有改变
ptrb->_ptra = ptra;
cout << ptra.use_count() << endl; // 打印结果:1
cout << ptrb.use_count() << endl; // 打印结果:1
/*
出main函数作用域,ptra和ptrb两个局部对象析构,分别给A对象和
B对象的引用计数从1减到0,达到释放A和B的条件,因此new出来的A和B对象
被析构掉,解决了“强智能指针的交叉引用(循环引用)问题”
*/
return 0;
}
//代码打印如下:
A()
B()
1
1
~B()
~A()