动态内存

132 阅读5分钟

智能指针 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;

image.png

设计思路

既然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工具,查看到资源都释放完毕,某没有内存泄露问题. image.png

设计后的收获

以上只是实现了一个基本的版本,更复杂的情况如多线程等方面没有考虑, 具体设计和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;