文章目录
裸指针的问题
new出的内存如果不进行释放,会导致内存泄露。
手写简易 智能指针
目的:保证能做到资源的自动释放
最简单的智能指针
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T *ptr = nullptr) : mptr(ptr) {}
~CSmartPtr()
{
delete mptr;
mptr = nullptr;
}
T& operator*() { return *mptr; }
T* operator->() { return mptr; }
private:
T* mptr;
}
使用:
class Test
{
public:
void test() {cout << "call Test::test() << endl; }
};
int main()
{
CSmartPtr<int> ptr1(new int);
*ptr1 = 20;
CSmartPtr<Test> ptr2(new Test());
ptr2->test(); // 等同于 (*ptr2).test()
}
但是随之而来的问题是,如何让两个指针一起管理同一块内存区域?
CSmartPtr<int> p1(new int);
CSmartPtr<int> p2(p1);
如果做的引用浅拷贝,在析构时指针double free。
如果做的是深拷贝:
CSmartPtr(const CSmartPtr<T> &src) // 深拷贝
{
mptr = new T(*src.mptr);
}
那么这两块指针会指向两块不同的内存空间。但是用户是想让p1和p2指针管理同一块资源,这样也是错的。所以深拷贝根本不行。
不带引用计数的智能指针
首先重写不带引用计数的智能指针。不带引用计数的智能指针有auto_ptr(C++不推荐使用),scoped_pte(C++11使用的很少),unique_ptr(C++11)。
不带引用计数的智能指针解决不了浅拷贝问题。
auto_ptr
auto_ptr:浅拷贝时,p1赋nullptr (源码中的release方法)
auto_ptr<int> p1(new int);
auto_ptr<int> p2(p1);
auto_ptr底层是只有最后一个指针可以使用,其他所有指针全部置nullptr。
像上面的例子,只有p2能用,p1已经nullptr了。
scoped_pte
浅拷贝时,报错,直接将拷贝构造删除了。
scoped_ptr(const scoped_ptr<T>&) = delete;
scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete;
unique_ptr
独占指针,只让一个指针管理资源。
虽然unique_ptr也将拷贝构造删除了,但是unique_ptr提供了移动构造。
unique_ptr(const unique_ptr<T>&) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
unique_ptr(unique_ptr<T> &&src)
unique_ptr<T>& operator=(unique_ptr<T> &&src)
unique_ptr<int> p1(new int);
unique_ptr<int> p2(std::move(p1)); // 得到当前变量的右值引用
这时p1已经不持有资源了(move了)
带引用计数的智能指针
shared_ptr, weak_ptr
带引用计数好处:多个智能指针可以管理同一个资源。
带引用计数的含义:给每一个对象资源,匹配一个引用计数
智能指针 =》 资源的时候 =》 引用计数+1
智能指针 =》不使用资源的时候 =》 引用计数-1 =》 != 0
当引用计数为0时资源释放了
手写带引用计数的智能指针CSmartPtr
template<typename T>
class RefCnt
{
public:
Refcnt(T *ptr = nullptr) : mptr(ptr)
{
if(mptr != nullptr)
{
mcount = 1;
}
}
void addRef() { mcount++; } //添加资源的引用计数
void delRef() { --mcount; }
private:
T *mptr;
int mcount;
}
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T *ptr = nullptr) : mptr(ptr)
{
mpRefCnt = new RefCnt<T>(mptr);
}
~CSmartPtr()
{
if(0 == mpRefCnt->delRef())
{
delete mptr;
mptr = nullptr;
}
}
CSmartPtr(const CSmartPtr& src) : mptr(ptr), mpRefCnt(src.mpRefCnt)
{
if(mptr != nullptr)
mpRefCnt->addRef();
}
CSmartPtr<T>& operate=(const CSmartPtr& src)
{
if(this == &src)
return *this; // 防止自赋值ptr3 = ptr3;
if(0 == mpRefCnt->delRef())
{
delete mptr;
}
mptr = src.mptr;
mpRefCnt = src.mpRefCnt;
mpRefCnt->addRef();
return *this;
}
T& operator*() { return *mptr; }
T* operator->() { return mptr; }
private:
T* mptr; // 指向资源的指针
RefCnt<T> *mpRefCnt; // 指向该资源引用计数对象的指针
}
int main()
{
CSmartPtr<int> ptr1(new int);
CSmartPtr<int> ptr2(ptr1);
CSmartPtr<int> ptr3;
ptr3 = ptr2;
*ptr1 = 20; // 这时指针1、2、3都是20
}
这个自己实现的CSmartPtr并不是线程安全的,因为设计到计数值的加减。
注意:
shared_ptr引用计数在什么情况下会计数减一?答:在赋值和析构的情况都会减一。
shared_ptr
强智能指针:可以改变资源的引用计数。
强智能指针循环引用问题(交叉引用):
问题主要内容:指针的引用计数永远不会减为零,对象也永远不会被销毁,造成new出来的资源无法释放,内存泄露。
示例代码:
#include <iostream>
#include <memory>
class B; // 前置声明,用于在类A中声明B的智能指针
class A {
public:
std::shared_ptr<B> b_ptr;
A() {
std::cout << "A constructor" << std::endl;
}
~A() {
std::cout << "A destructor" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> a_ptr;
B() {
std::cout << "B constructor" << std::endl;
}
~B() {
std::cout << "B destructor" << std::endl;
}
};
int main() {
std::shared_ptr<A> a_ptr = std::make_shared<A>();
std::shared_ptr<B> b_ptr = std::make_shared<B>();
a_ptr->b_ptr = b_ptr;
b_ptr->a_ptr = a_ptr; // 循环引用,导致内存泄漏
return 0;
}
问题描述:
两个类 A 和 B。类 A 包含一个指向类 B 的智能指针 b_ptr,而类 B 包含一个指向类 A 的智能指针 a_ptr。
在 main 函数中,我们创建了两个对象 a_ptr 和 b_ptr,然后将它们互相连接形成循环引用
解决方案:
定义对象的时候用强智能指针,引用对象的时候用弱智能指针。
weak_ptr
弱智能指针:
- 不能改变资源的引用计数。
- 他只负责监控指向的内存,只是一个观察者,没有裸指针的
.
和->
功能。
weak_ptr 观察 shared_ptr,shared_ptr再管理资源(内存)
但是弱智能指针可以提升为强智能指针(可能会失败)
shared_ptr<A> ps = _ptra.lock();
if(ps != nullptr) ps->testA();
多线程访问共享对象的线程安全问题
子线程
// 子线程
void handler01(weak_ptr<A> pw)
{
std::this thread::sleep for(std::chrono::seconds(2));
shared_ptr<A> sp = pw.lock(); // 升级!
if (sp != nullptr) sp->testA(); // 看升级结果,侦测A对象是否存活(p已经出作用域了)
else cout << "A对象已经析构,不能再访问!” << endl;
}
main线程
int main()
{
{
shared ptr<A> p(new A());
thread tl(handler01, weak ptr<A>(p));
t1.detach();
}
std::this thread::sleep for(std::chrono::seconds(20));
// 阻塞等待子线程结束
// t1.join();
return 0;
}
此时结构为:A对象已经析构,不能再访问!