【C++智能指针】unique_ptr的源码详解以及应用实例

裸指针

内存泄露:

  1. 堆内存泄露:结束时,没有释放归还给os
  2. 资源泄露:久而久之,系统资源(文件描述符、socket)会爆掉

代码举例

#include<iostream>
#include<memory>
#include<stdio.h>
#include<atomic>
using namespace std;
class Int
{
private:
	int value;
public:
	explicit Int(int x = 0) :value(x) {
		cout << "creat Int: " << this << endl;
	}
	~Int() { cout << "Destroy Int: " << value << endl; }
	void SetValue(int x = 0) {
		value = x;
	}
	int GetValue() const {
		return value;
	}
	void Print() const {
		cout << "Value: " << value << endl;
	}
};

int main()
{
	Int* p = new Int(3);
	delete p;//为什么要delete,只要new了就要delete
	//但如果代码太多了,忘了delete怎么办
	//就要用智能指针,其实本质就是 RAII特性的管理器,把指针封装了进去
    return 0;
}

在这里插入图片描述

裸指针的缺点

  1. 难以区分指针指向的是单个对象还是一组对象: 裸指针本身并不提供任何关于指针指向的对象数量的信息。因此,在使用裸指针时,很难确定指针指向的是单个对象还是数组(一组对象)。这可能导致在处理指针时出现假设错误,从而引发错误或未定义行为。
  2. 无法确定是否应该销毁指针: 裸指针无法告知程序员它是否"拥有"指向的对象。因此,在使用完指针后,程序员无法确定是否应该销毁指针以及其指向的对象。这可能导致内存泄漏或者试图释放非法内存。
  3. 难以确定销毁指针的方法: 即使程序员确定需要销毁指针,也很难确定应该使用何种方式来销毁指针。是使用 delete 关键字删除指向的单个对象?还是需要调用某个特定的销毁函数来释放资源?缺乏明确的规范可能导致内存泄漏或者非法内存释放。
  4. 难以确定使用 delete 还是 delete[] 如果确定需要使用 deletedelete[] 来释放资源,那么由于无法确定指针指向的是单个对象还是数组,就无法确定应该使用哪种删除方式。使用错误的方式可能导致内存泄漏或未定义行为。
  5. 难以保证在所有路径中进行正确的销毁操作: 即使程序员认识到了需要销毁指针,并且确定了正确的销毁方式,但由于程序的复杂性,很难保证在所有可能的路径(例如分支结构、异常处理路径等)中,都执行了正确的销毁操作。遗漏任何一次销毁操作都可能导致内存泄漏或未定义行为。
  6. 无法分辨指针是否处于悬挂状态: 裸指针本身无法提供关于其是否处于悬挂状态(dangling)的信息。悬挂指针是指指向已经释放的内存的指针,使用悬挂指针会导致未定义行为。由于裸指针本身不包含指向对象的生命周期信息,因此无法准确判断指针是否处于悬挂状态。

引入RAII

RAII 的核心思想是使用局部对象来管理资源,以确保资源在对象生命周期结束时被正确释放,从而避免资源泄漏和资源管理错误。 资源可以是各种类型的资源,包括内存(heap)、文件句柄、网络套接字、互斥量等等。局部对象指的是存储在栈上的对象,其生命周期由其所在的作用域来管理,当对象离开作用域时,其析构函数会被自动调用,从而释放相关资源。

整个RAlI过程总结四个步骤:

1.设计一个类封装资源

2.在构造函数中初始化

3.在析构函数中执行销毁操作

4.使用时定义一个该类的局部对象

举例拥有 RAII 特性的管理器包括:

  1. 智能指针(如 std::unique_ptrstd::shared_ptrstd::weak_ptr:智能指针用于管理动态分配的内存,通过将内存资源绑定到对象的生命周期,确保在对象销毁时释放内存,避免内存泄漏。
  2. 文件管理器:通过 RAII 可以实现文件资源的自动管理,例如 std::fstreamstd::ifstreamstd::ofstream 等类,它们在构造函数中打开文件,在析构函数中关闭文件,确保文件资源在对象销毁时被正确释放。
  3. 互斥锁管理器:利用 RAII 可以实现互斥锁资源的自动管理,例如 std::lock_guardstd::unique_lock 等类,它们在构造函数中锁定互斥锁,在析构函数中释放互斥锁,确保在对象离开作用域时释放互斥锁

由于一个对象不能被析构两次,因此部分RAII管理器将深拷贝和深赋值delete删除掉,但是仍有例如:share_ptr 有深拷贝和深赋值的机制

下面开始刨析unique_ptr的源码

unique_ptr

"独占型"智能指针:一个对象和其他对应的资源同一时间只可以被一个unique_ptr对象拥有,一旦拥有者销毁或者变为empty或者开始拥有另一个对象的地址,先前拥有的那个对象就会被销毁,其任何相应资源也会被释放

特点

  1. 两个指针不可以同时指向一个资源,因此需要将深拷贝深赋值delete
  2. 可以拥有移动构造和移动赋值
  3. 为了解决裸指针无法区分单个对象和一组对象的缺点,引入删除器类型
  4. 在容器里保存指针是安全的
  5. unique_ptr具有->和*运算符重载符,因此可以像普通指针一样使用

源码

#include<iostream>
#include<memory>
#include<stdio.h>
#include<atomic>
#include <thread>
using namespace std;
class Int
{
private:
	int value;
public:
	explicit Int(int x = 0) :value(x) {
		cout << "creat Int: " << this << endl;
	}
	~Int() { cout << "Destroy Int: " << value << endl; }
	void SetValue(int x = 0) {
		value = x;
	}
	int GetValue() const {
		return value;
	}
	void Print() const {
		cout << "Value: " << value << endl;
	}
};

template <class _Ty>
struct my_default_deleter
{
	void operator()(_Ty* ptr)const
	{
		cout << "ptr : " << ptr << endl;
		delete ptr;
	}
};

struct ThreadJoin
{
	void operator()(std::thread* p)const
	{
		if (p->joinable())
		{
			p->join();
		}
		cout << "线程资源地址 : " << p << endl;
		delete p;
	}
};


template <class _Ty, class _Dx = my_default_deleter<_Ty> >
class my_unique_ptr
{
private:
	using pointer = _Ty*;
	using element_type = _Ty;
	using deleter_type = _Dx;
	pointer mptr;
	deleter_type mDeleter;
public:
	explicit my_unique_ptr(pointer p = nullptr) :mptr(p)
	{
		cout << "creat unique_ptr: " << "指针地址:" << this << "  对象资源地址: " << this->get() << endl;
	}
	~my_unique_ptr()
	{
		if (get() != nullptr)
		{
			reset();
		}
		cout << "Destroy unique_ptr: " << "指针地址:" << this << "  对象资源地址: " << this->get() << endl;
	}
	my_unique_ptr(my_unique_ptr& p) = delete;
	my_unique_ptr& operator=(my_unique_ptr& p) = delete;
	explicit my_unique_ptr(my_unique_ptr&& p)
	{
		cout << "移动拷贝" << endl;
		reset(p.release());
	}
	my_unique_ptr& operator=(my_unique_ptr&& p)
	{
		cout << "移动赋值" << endl;
		if (this != &p)
		{
			reset(p.release());
		}
		return *this;
	}
	pointer release()
	{
		pointer old = this->get();
		this->mptr = nullptr;
		return old;
	}
	void reset(pointer p = nullptr)
	{
		if (get() != nullptr)
		{
			getDeleter()(get());
		}
		this->mptr = p;
	}
	pointer get()const
	{
		return this->mptr;
	}
	operator bool()const
	{
		return get() != nullptr;
	}
	pointer operator->()const
	{
		return get();
	}
	element_type& operator*()const
	{
		return *get();
	}

	deleter_type getDeleter() const { return deleter_type(); }
};


void func()
{
	cout << "func  ~  " << endl;
}
int main()
{
	my_unique_ptr<Int> a(new Int(10));
	//my_unique_ptr<Int> c = (new Int(10));//这是隐式转换,加明确关键字即可
	my_unique_ptr<Int> b(std::move(a));
	a = std::move(b);//移动赋值
	//my_unique_ptr<Int> c = std::move(b);//这是隐式转换,加明确关键字即可

	//my_unique_ptr<std::thread, ThreadJoin> thre(new thread(func));
	return 0;
}

上面是最基础的源码

还有缺点,比如无法进行泛型操作,特化、完全特化

const 修饰的变量必须定义值

引入泛化特性

#include<iostream>
#include<memory>
#include<stdio.h>
#include<atomic>
#include <thread>
using namespace std;

template<class T,class U>
class C//泛化版本
{
public:
	void f() {
		cout << "T" << endl;
	}
};

template<class U>
class C<int, U>//类模板实例化版本
{
public:
	void f() {
		cout << "T U" << endl;
	}
};

template<class T, class U>
class C<T*, U*>//类模板实例化版本
{
public:
	void f() {
		cout << "T* U*" << endl;
	}
};

template <class T>
void func(T &x)
{
	T a;
	cout << "T name : " << typeid(T).name() << endl;
	cout << "a name : " << typeid(x).name() << endl;
}
int main()
{
	C<int, int*> c1;
	C<int*, int*> c2;
	C<char, double> c3;
	C<int, char> c4;
	c1.f();
	c2.f();
	c3.f();
	c4.f();

	return 0;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对此我们对模板元编程进入到入门阶段

下面处理特化

对其他非heao内存管理的特化也可以仿写删除器来调用,例如:线程和文件

对unique_ptr特化

template <class _Ty>
struct my_default_deleter
{
	void operator()(_Ty* ptr)const
	{
		cout << "ptr : " << ptr << endl;
		delete ptr;
	}
};

template <class _Ty>
struct my_default_deleter<_Ty[]>
{
	void operator()(_Ty* ptr)const
	{
		cout << "ptr : " << ptr << endl;
		delete []ptr;
	}
};

template <class _Ty, class _Dx = my_default_deleter<_Ty> >
class my_unique_ptr
{
private:
	using pointer = _Ty*;
	using element_type = _Ty;
	using deleter_type = _Dx;
	pointer mptr;
	deleter_type mDeleter;
public:
};

template <class _Ty, class _Dx >
class my_unique_ptr<_Ty[], _Dx>
{
private:
	using pointer = _Ty*;
	using element_type = _Ty;
	using deleter_type = _Dx;
	pointer mptr;
	deleter_type mDeleter;
public:
};
//下面我们就可以使用 一组对象了

面试题:会在内部倒一下

在这里插入图片描述

在C++中,泛型类(也称为模板类)允许您定义一个类,其中的某些类型或值可以作为参数进行泛化。特化类则是针对特定类型或值进行了定制的类模板实例化版本。

当您删除了泛型类的定义时,也就删除了模板类的定义。因此,如果特化类依赖于泛型类的定义,当泛型类被删除时,特化类将无法找到其依赖项,从而无法使用。

介绍 unique_ptr <Int> n = make_unique<Int>(10);

make_unqiue是如何实现的

我们思考…在args前面和后面的区别

template <class _Ty, class ..._Types>
my_unique_ptr<_Ty> my_unique_make(_Types&& ...args)
{
	return my_unique_ptr<_Ty>(new _Ty(std::forward<_Types>(args)...));
    //上面的代码其实等同于下面的代码
	//return my_unique_ptr<Int> (new Int(10));
}
  • ... 是 C++ 中的展开运算符(pack expansion operator),用于将参数包展开成参数序列。
  • _Types 是一个模板参数包(template parameter pack),用于接受零个或多个模板参数。
  • args 是函数模板的参数,它表示一个参数包,用于接受函数调用时传入的参数。

…在args的前面和在args的后面区别:

…运算符在args前面表示将args中的参数打包为一个参数包,而在args后面表示将参数包展开为多个参数。这样可以确保传递给构造函数的参数数量和类型与参数包中的参数数量和类型匹配,从而正确地构造对象

int main()
{
	my_unique_ptr<Int> a = my_unique_make<Int>(10);//要使用它必须去掉移动构造的 explicit 显式关键字
}

unique_ptr不能和hash_map结合

哈希容器在插入或移除元素时需要执行键和值的拷贝操作,因此要求键和值类型必须支持复制或移动语义。而由于 std::unique_ptr 的独占性质,它不能被复制,只能被移动,因此无法直接用作哈希容器的键或值。

为了在哈希容器中使用类似 std::unique_ptr 的智能指针,我们可以考虑以下几种方法:

  1. 使用 std::shared_ptr 如果需要在哈希容器中共享所有权,可以考虑使用 std::shared_ptrstd::shared_ptr 允许多个指针共享对同一对象的所有权,并且支持复制语义,因此可以直接用作哈希容器的键或值。
  2. 使用裸指针: 如果确定不会出现所有权转移的情况,并且需要更高效的内存管理,可以考虑使用裸指针(即指针类型为 T*)。但是要注意在使用裸指针时需要自行管理内存的分配和释放,避免出现悬挂指针或内存泄漏等问题。
  3. 自定义包装类: 可以编写自定义的智能指针类来满足特定的需求,并在其中实现适当的拷贝或移动语义以支持在哈希容器中使用。

应用

简单工厂的代码实例

#include <iostream>
#include <stdexcept>
#include <cstddef>
#include <string>
#include <vector>
using namespace std;

class IShape
{
public:
	virtual void draw() = 0;
	virtual void area() = 0;
	virtual ~IShape() {}
	class BadShapeCreation : public logic_error
	{
	public:
		BadShapeCreation(string type)
			: logic_error("Cannot create type " + type) {}
	};
	static unique_ptr<IShape> factory(const string& type) throw(BadShapeCreation);
};

class Circle : public IShape
{
private:
	Circle() {} // Private constructor
	friend class IShape;
private:
	float radius;
	const static float PI;
public:
	void draw() { cout << "Circle::draw" << endl; }
	void area() { cout << "Circle::area: " << PI * radius * radius << endl; }
	~Circle() { cout << "Circle::~Circle" << endl; }
	float& Radius() { return radius; }
	const float& Radius() const { return radius; }
};

const float Circle::PI = 3.14;
class Square : public IShape
{
private:
	Square() {}
	friend class IShape;
private:
	float sideLength;
public:
	void draw() { cout << "Square::draw" << endl; }
	void area() { cout << "Square::area: " << sideLength * sideLength << endl; }
	~Square() { cout << "Square::~Square" << endl; }
	float& SideLength() { return sideLength; }
	const float& SideLength() const { return sideLength; }
};

unique_ptr<IShape> IShape::factory(const string& type)   throw(IShape::BadShapeCreation)
{
	unique_ptr<IShape> sp = nullptr;
	if (type == "Circle")
	{
		sp.reset(new Circle());
	}
	else if (type == "Square")
	{
		sp.reset(new Square());
	}
	else
	{
		throw BadShapeCreation(type);
	}
	return sp;
}

const char* sl[] = { "Circle", "Square", "Square",  "Circle", "Circle", "Circle", "Square" };

int main()
{
	//vector<IShape *> shapes; 这样可以防止内存泄露
	std::vector<std::unique_ptr<IShape> > shapes;
	try
	{
		for (size_t i = 0; i < sizeof sl / sizeof sl[0]; i++)
		{
			shapes.push_back( (IShape::factory(sl[i])));
		}
	}
	catch (IShape::BadShapeCreation e)
	{
		cout << e.what() << endl;
		return EXIT_FAILURE;
	}
	for (size_t i = 0; i < shapes.size(); i++)
	{
		Circle* cp = dynamic_cast<Circle*>(shapes[i].get());

		Square* sp = dynamic_cast<Square*>(shapes[i].get());
		if (nullptr != cp)
		{
			cp->Radius() = rand() % 100;
			cp->draw();
			cp->area();
		}
		if (nullptr != sp)
		{
			sp->SideLength() = rand() % 100;
			sp->draw();
			sp->area();
		}
	}

	return 0;
} 
  • 28
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bright_汉堡包

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值