智能指针 smart pointer
概述
为了更容易安全管理内存,C++提供了三种智能指针管理内存. shared_ptr允许多个指针指向通过一个对象,unique_ptr则独占所指向的的对象. weak_ptr用来协助shared_ptr,是一种弱引用,指向shared_ptr管理的对象.
shared_ptr
make_shared函数
动态创建一个对象,并用shared_ptr管理,最安全的方法是调用make_shared函数
,make_shared返回的就是shared_ptr指针.
shared_ptr<A> sp = make_shared<A>();
shared_ptr的拷贝,赋值
auto ps = make_shared<int>(12); //赋值
auto q(ps); //拷贝
引用计数增1:
- 初始化1
auto ps = make_shared<int>(12);// ps的引用计数为1 - 拷贝
auto q(ps) ;//ps的引用计数加1,q的引用计数和ps想等 - 作为参数传递
void fun(share_ptr<int> p); fun(ps); - 赋值
p = q;// q的引用计数加1 - 初始化2: new创建一个对象并用指针(其实这个应该可以通过make_shared实现)
引用计数减去1:
- shared_ptr被赋予新值:
p = q;// q的引用计数减去1 - shared_ptr本身被销毁: 如离开函数作用域,调用析构函数
设计思路
利用一个计数器(或其他数据结构)记录了多少指针共享同一个对象, 当计数器值减小为0,shared_ptr自动销毁管理的对象,并释放关联的内存.
weak_ptr
它不控制所指对象生命周期(对象占用的资源不通过它申请,释放),指向的是一个shared_ptr管理的对象. weak_ptr不改变shared_ptr的引用计数. 一旦shared_ptr的引用计数减为0,对象资源便释放,此时并不关心是否有weak_ptr指向这个对象. weak_ptr的这个引用计数记录的是有多少个shared_ptr共享对象.
weak_ptr的出现主要是解决shared_ptr带来的循环引用的问题.循环引用指的是shared_ptr指向自身了.例子中 p1->ps = p2; p2->pf = p1;,得到的效果就是p1->ps->pf = p1通过某种手段指向了自己,离开作用域前引用计数都为2,离开就引用计数就会减为1,这个时候不会释放资源. 这个问题就是循环引用导致的: 解决起来就是通过weak_ptr,如果p1->ps = p2不增加p2的引用计数就搞定了,即只要一个shared_ptr的引用计数在离开作用域前为1,就会打破这种死锁现象.
class Son;
class Father {
public:
shared_ptr<Son> ps;
};
class Son {
public:
shared_ptr<Father>pf;
};
shared_ptr<Father> p1(new Father());
shared_ptr<Son>p2(new Son());
cout << "父类指针引用计数:" << p1.use_count() << endl;
cout << "子类指针引用计数:" << p2.use_count() << endl;
p1->ps = p2;
p2->pf = p1;
cout << "父类指针引用计数:" << p1.use_count() << endl;
cout << "子类指针引用计数:" << p2.use_count() << endl;
设计思路
既然shared_ptr能够赋值给weak_ptr,那么二者包含的数据部分应该完全一样. 这样可以设计以及基类,包含指向引用对象的指针和一个共享计数器. 而weak_ptr的拷贝构造函数,赋值运算函数等实现自己的版本
unique_ptr
独占对象,不存在什么引用计数. 既然独占,那么拷贝构造等函数就不能被调用,可以设置为delete. 在这个函数的析构函数里面释放资源.
.
三类智能指针的实现
(其实折腾了挺久)
代码实现
#pragma once
#include <assert.h>
#include<iostream>
template<typename Ty>
class BasePtr {
protected:
Ty* refobj;
size_t* refcnt;
public:
BasePtr():refobj(nullptr),refcnt(nullptr) {}
explicit BasePtr(const BasePtr& rhs) = delete; //因为派生类逻辑不一样
BasePtr& operator=(const BasePtr& rhs) = delete;
Ty* operator->() const;
Ty& operator*() const;
~BasePtr() = default; //析构函数不能为delete,但必须得重写
public:
Ty* getObjPtr()const;
size_t use_count()const {
if (refcnt == nullptr) return 0;
else return *refcnt;
}
protected:
void weakCopyFrom(const BasePtr& other);
void addRef() {
if (refobj != nullptr) {
if (refcnt == nullptr) {
refcnt = new size_t(0); //这个写在基类里感觉不好
}
++(*refcnt);
}
}
void decRef() {
if(refcnt != nullptr)
if (--(*refcnt) == 0) {
delete refobj;
delete refcnt;
refobj = nullptr;
refcnt = nullptr;
std::cout << "资源已经释放" << std::endl;
}
}
};
template<typename Ty>
void BasePtr<Ty>::weakCopyFrom(const BasePtr& other)
{
if (other.getObjPtr() != nullptr) {
this->refobj = other.refobj;
this->refcnt = other.refcnt;
}
else {
this->refobj = nullptr;
this->refcnt = nullptr;
}
}
template<typename Ty>
Ty& BasePtr<Ty>::operator*()const
{
return *(this->refobj);
}
template<typename Ty>
Ty* BasePtr<Ty>::operator->()const
{
return this->refobj;
}
template<typename Ty>
Ty* BasePtr<Ty>::getObjPtr() const
{
return refobj;
}
template<typename Ty>
class SharedPtr :public BasePtr<Ty> {
public:
//SharedPtr();
explicit SharedPtr(Ty* objptr = nullptr);
SharedPtr(const SharedPtr& other);
SharedPtr& operator=(const SharedPtr& other);
~SharedPtr();
};
template<typename Ty>
SharedPtr<Ty>::SharedPtr(Ty* objptr /*= nullptr*/)
{
if (objptr != nullptr) {
this->refobj = objptr;
this->addRef();
}
else {
this->refcnt = nullptr;
this->refobj = nullptr;
}
}
//template<typename Ty>
//SharedPtr<Ty>::SharedPtr() :BasePtr<Ty>(){}
template<typename Ty>
inline SharedPtr<Ty>::SharedPtr(const SharedPtr& other)
{
this->weakCopyFrom(other);
this->addRef();
}
template<typename Ty>
inline SharedPtr<Ty>& SharedPtr<Ty>::operator=(const SharedPtr<Ty>& other) // p = q; p都存在,q可能为nullptr
{
this->decRef();
this->weakCopyFrom(other);
this->addRef();
return *this;
}
template<typename Ty>
inline SharedPtr<Ty>::~SharedPtr()
{
this->decRef();
}
template<typename Ty>
class WeakPtr :public BasePtr<Ty> {
public:
WeakPtr();
WeakPtr(const WeakPtr& other);
WeakPtr(const SharedPtr<Ty>& other);
WeakPtr(const Ty* ptr) = delete;
WeakPtr& operator=(const WeakPtr& other);
~WeakPtr();
};
template<typename Ty>
WeakPtr<Ty>& WeakPtr<Ty>::operator=(const WeakPtr<Ty>& other)
{
this->weakCopyFrom(other);
return *this;
}
template<typename Ty>
inline WeakPtr<Ty>::~WeakPtr(){} //资源不是通过你释放的,引用计数你也无权管
//无法访问SharedPtr的成员,那么需要通过手段:基类提供接口
template<typename Ty>
WeakPtr<Ty>::WeakPtr(const SharedPtr<Ty>& other)
{
this->weakCopyFrom(other);
}
template<typename Ty>
WeakPtr<Ty>::WeakPtr(const WeakPtr& other)
{
this->weakCopyFrom(other);
}
template<typename Ty>
WeakPtr<Ty>::WeakPtr() :BasePtr<Ty>() {};
//这个类不涉及引用计数等,本着节约资源的原因,所以重新设计
template<typename Ty>
class UniquePtr {
private:
Ty* refobj;
public:
UniquePtr();
UniquePtr(const UniquePtr&other) = delete;
UniquePtr& operator=(const UniquePtr& other) = delete;
UniquePtr(Ty* ptr);
~UniquePtr();
public:
void reset(Ty* ptr = nullptr); //重置
};
template<typename Ty>
UniquePtr<Ty>::~UniquePtr()
{
if (refobj) {
delete refobj;
refobj = nullptr;
}
}
template<typename Ty>
UniquePtr<Ty>::UniquePtr(Ty* ptr) :refobj(ptr) {}
template<typename Ty>
void UniquePtr<Ty>::reset(Ty* ptr /*= nullptr*/)
{
if (refobj != ptr) {
if (refobj != nullptr) {
delete refobj;
}
refobj = ptr;
}
}
template<typename Ty>
UniquePtr<Ty>::UniquePtr() :refobj(nullptr) {};
测试
最后在main函数里面测试相应的功能
#include<iostream>
#include <vld.h>
#include "SmartPtr.h"
using namespace std;
//void TestBasePtr() {
// BasePtr<int> obj;
//}
void testSharedPtr1() {
cout << "无参默认构造" << endl;
SharedPtr<int>ptr1;
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
cout << "--------------------------------" << endl;
cout << "有参默认构造" << endl;
SharedPtr<int>ptr2(new int(1));
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
cout << "--------------------------------" << endl;
cout << "空赋值给非空" << endl;
ptr2 = ptr1;
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
cout << "--------------------------------" << endl;
cout << "非空赋值给空" << endl;
//ptr2 = new int(3); //应该禁止这种操作,所以有个函数加上explicit
SharedPtr<int>ptr3(new int(2));
ptr2 = ptr3;
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
cout << "ptr3的引用计数:" << ptr3.use_count() << endl;
cout << "--------------------------------" << endl;
cout << "增加引用" << endl;
auto ptr4(ptr2);
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
cout << "ptr3的引用计数:" << ptr3.use_count() << endl;
cout << "ptr4的引用计数:" << ptr4.use_count() << endl;
cout << "--------------------------------" << endl;
cout << "自己赋值给自己" << endl;
ptr2 = ptr2;
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
cout << "ptr3的引用计数:" << ptr3.use_count() << endl;
cout << "ptr4的引用计数:" << ptr4.use_count() << endl;
cout << "--------------------------------" << endl;
cout << "清空其中一个" << endl;
ptr3 = ptr1;
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
cout << "ptr3的引用计数:" << ptr3.use_count() << endl;
cout << "ptr4的引用计数:" << ptr4.use_count() << endl;
cout << "--------------------------------" << endl;
}
void testSharedPtr2() {
class Son;
class Father {
public:
WeakPtr<Son> ps;
int a;
};
class Son {
public:
SharedPtr<Father>pf;
int b;
};
SharedPtr<Father> p1(new Father());
SharedPtr<Son>p2(new Son());
cout << "父类指针引用计数:" << p1.use_count() << endl;
cout << "子类指针引用计数:" << p2.use_count() << endl;
p1->ps = p2;
p2->pf = p1;
cout << "父类指针引用计数:" << p1.use_count() << endl;
cout << "子类指针引用计数:" << p2.use_count() << endl;
//int* p = new int(3)
}
void TestWeakPtr() {
WeakPtr<int>w1;
cout << w1.use_count() << endl; // 0
WeakPtr<int>w2(w1);
cout << w1.use_count() << endl; // 0
cout << w2.use_count() << endl; // 0
SharedPtr<int>ps(new int(1));
WeakPtr<int>w3(ps);
cout << ps.use_count() << endl; // 1
cout << w3.use_count() << endl; // 1
w3 = w3;
cout << w3.use_count() << endl; // 1
w2 = w3;
cout << w2.use_count() << endl; // 1
cout << w3.use_count() << endl; // 1
}
void testUnique() {
UniquePtr<int>ptr;
ptr.reset();
UniquePtr<int>ptr2(new int(2));
}
int main()
{
shared_ptr<int>ptsd;
testSharedPtr1();
testSharedPtr2();
TestWeakPtr();
testUnique();
system("pause");
return 0;
}
执行结果
最后结合VLD工具,查看到资源都释放完毕,某没有内存泄露问题.
设计后的收获
以上只是实现了一个基本的版本,更复杂的情况如多线程等方面没有考虑, 具体设计和VS2019源码也存在比较大的区别. 但对于智能指针的原理有了更清晰的了解.当然也踩了不少坑,这里总结下:
- 基类的private成员在派生类中无法方法(无论哪种继承方式)
- 基类的ptotected成员能提供给派生类访问,又不至于在类外暴露接口
- 基类派生类通过类模板实现,在访问派生类中定义的成员时,需要通过this指针(这个在VS2019以及GCC中都是这样)
- 当实现
Ty& operator=(const Ty &other)的时候, 考虑下ptr = ptr是否会出问题.
allocator类
作用:实现内存分配和对象构造的分离.
这个在STL的设计中很常见. 它根据指定的类型分配恰当大小的内存和对齐位置.
allocator<string>alloc;
auto const p = alloc.allocate(4);
主要的接口函数
即使用的时候,用allocate分配空间,然后调用construct调用类的构造函数,最后结束后,先用destroy析构掉创建的对象,然后释放内存. (先析构,保证对象指针不会成为空悬)
注意,必须保证开辟的空间足够,否则很可能发生堆错误
auto p = alloc.allocate(v.size() * 2);
auto q = uninitialized_copy(v.begin(), v.end(), p);
uninitialized_fill_n(q, v.size(), 8);
for (int i = 0; i < 2 * v.size(); ++i) {
cout << *(p + i) << " "; // 1 3 5 8 8 8
}
cout << endl;