智能指针之unique_ptr

前言

Java说:内存管理是如此重要,不能给程序员管理。
C++:内存管理是如此重要,必须由程序员管理。

内存管理技术分为自动内存管理(Java、Python等)和半自动内存管理(C++)。C++支持半自动内存管理的理论基础是值语义;技术基础是智能指针。
C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr其中后三个是C++11支持,并且第一个已经被C++11弃用。
智能指针主要用于管理在上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。

什么情况下使用unique_ptr

智能指针试图解决资源的所有权问题。
一个指针对资源所有权的要求可以分为三种情况:

  1. 不需要资源的所有权:使用weak_ptr或者裸指针
  2. 独占资源的所有权:unique_ptr
  3. 共享资源的所有权:shared_ptr

unique_ptr实现独占式拥有概念,保证同一时间内只有一个智能指针可以指向该对象。当unique_ptr析构时,会调用其管理的指针的delete函数或析构函数。

unique_ptr基础知识

构造、复制、赋值

//unique_ptr的申明
template<class _Ty,
	class _Dx>	// = default_delete<_Ty>
	class unique_ptr

unique_ptr有两个模板参数:指针类型和删除函数deletor类型,指针类型必填,deletor类型选填。

unique_ptr不可复制、不可赋值,只能移动

// unique_ptr constructor example
#include <iostream>
#include <memory>

int main () {
  std::default_delete<int> d;
  std::unique_ptr<int> u1;    // u1 is null
  std::unique_ptr<int> u3 (new int); //u3 is not null
  std::unique_ptr<int, typedecl(d)> u4 (new int, d); // u4 is not null
  std::unique_ptr<int> u5 (new int, std::default_delete<int>()); //u5 is not null
  //移动复制构造函数
  std::unique_ptr<int> u6 (std::move(u5)); //u6 is not null,u5 is null
  //移动赋值构造函数
  u1 = std::move(u3);  // u1 is not null, u3 is null;
  return 0;
}

常用成员函数

get: 返回管理的指针
operator bool:检查是否为空
release:解除与管理指针的关联,并返回管理的指针
reset:删除已管理的指针,设置新的指针
swap:交换管理的指针,一般不使用。

判断指针是否为空

//判断指针是否为空,方法一
unique_ptr<int> u1;
if(!u1)
{
}
//方法二
if(u1 == nullptr)
{
}

自定义delete函数

如果不设置自定义delete函数,unique_ptr析构管理指针时,调用改指针对象的默认析构函数。如果设置了deletorunique_ptr将调用deletor函数来析构管理指针。

设置方法

deletor是一个执行体,接受一个参数,参数类型为_T*,deletor可以是函数指针,lamda表达式或者仿函数。deletorunique_ptr类型的一部分。同样指针类型,不同的deletorunique_ptr类型不一样。

class A;
auto MyDeletor1 = [](A *a)
{
	//do something
	delete a;
};
unique_ptr<A, decltype(MyDeletor1)> p1(new A, MyDeletor1);
deletor对unique_ptr大小

不带deletorunique_ptr大小与裸指针大小相同,带有deletorunique_ptr大小等于裸指针大小加deletor大小。

size_t size1 = sizeof(unique_ptr<int>);   //与裸指针大小一致,size1 = 4
size_t size2 = sizeof(unique_ptr<int, void(*)(int)>; // size2 = 8

在windows 32位机器上各种可调用体的大小:

auto MyDeletor = [](A *p)
{
	delete p;
};
size_t size1 = sizeof(decltype(MyDeletor)); // 没有捕获变量的lamda表达式,size1 = 1
size_t size2 = sizeof(void(*)(int*));  //函数指针, size2 = 4
size_t size3 = sizeof(function(void(void)); //function, size3=24

实践

资源守卫:局部变量

在一个函数中需要使用临时创建某个对象,但是对象较大,不适合在创建到栈上。

bool func()
{
	//Object为一个较大的对象
	unique_ptr<Object> object(new Object);
	...
	if(expression)
		return false;
	do something
	...
	return true;
}

资源守卫:文件句柄,设置Delete函数

打开配置文件,读取内容,并关闭文件

void read_file()
{
	unique_ptr<FILE, decltype(fclose)> fp(fopen("test.txt", "r"), fclose);
	if (!fp)
	{
		//handle error
		return;
	}
	//read file content and handle 
	if(error occurs)
	{
	    return;
	}
}

使用deletor跟踪资源释放

class A;

auto DeleteA = [](A* a)
{
	<Log Delete A; >
	delete A;
};

unique_ptr<A, decltype<DeleteA>> CreateA()
{
	<Log Create A;>
	return unique_ptr<A, decltype<DeleteA>>(new A, DeleteA);
}

独占资源:成员变量

例如,每个学生都有一张学生卡;并且一张学生卡只能分派给一个学生。

class Card;
class Student
{
public:
	Student()
	{
		m_card.reset(new Card());
	}
	unique_ptr<Card> m_Card;
};

所有权转移:事件队列

在事件队列中,一个事件同一时间只有一个拥有者。

class Event
{
//Event成员、方法
};

class Worker
{
public:
	void Start()
	{
		m_thread = thread(bind(&Worker::Run, this));
	}

	void AddEvent(unique_ptr<Event> && event)
	{
		std::lock_guard<std::mutex> guard(m_lock);
		m_queue.push(std::forward<unique_ptr<Event>>(event));
	}

private:
	void Run()
	{
		auto GetEvent = [&]()->unique_ptr<Event>
		{
			std::lock_guard<std::mutex> guard(m_lock);
			unique_ptr<Event> event;
			if (!m_queue.empty())
			{
				event = std::move(m_queue.front());
				m_queue.pop();
			}
			return event;
		};

		for (auto event = GetEvent(); event != nullptr; event = GetEvent())
		{
			//handle event
		}
	}

private:
	std::mutex m_lock;
	queue<unique_ptr<Event>> m_queue;

	std::thread m_thread;
};


class EventProducer
{
public:
	void ProduceAnEvent()
	{
		unique_ptr<Event> event(new Event);
		//设置Event的属性

		m_worker->AddEvent(std::move(event));
	}
private:
	Worker *m_worker;
};

作为对比:下面是使用裸指针的代码。

class Event
{
};

class Worker
{
public:
	void Start()
	{
		m_thread = thread(bind(&Worker::Run, this));
	}

	void AddEvent(Event* event)
	{
		if (not started)
		{
			//陷阱:可能忘记delete
			delete event;
			return;
		}

		std::lock_guard<std::mutex> guard(m_lock);
		m_queue.push(event);
	}

private:
	void Run()
	{
		auto GetEvent = [&]()->Event*
		{
			std::lock_guard<std::mutex> guard(m_lock);
			if (!m_queue.empty())
			{
				Event* event = m_queue.front();
				m_queue.pop();
				return event;
			}
			return nullptr;
		};

		for (auto event = GetEvent(); event != nullptr; event = GetEvent())
		{
			//handle event
			if(expression)
			{
				//陷阱,有可能忘记delete
				delete event;
				continue;
			}
			delete event;
		}
	}

private:
	std::mutex m_lock;
	queue<> m_queue;
	std::thread m_thread;
};

class EventProducer
{
public:
	void ProduceAnEvent()
	{
		Event *event = new Event;
		//设置Event的属性

		m_worker->AddEvent(event);
	}
private:
	Worker *m_worker;
};

总结

  • unique_ptr的速度非常快,几乎与裸指针一样。
  • 不可复制,不可赋值,只能移动。
  • 不要与裸指针混合使用。特殊情况除外,例如需要调用第三方函数,但是第三方函数只接受裸指针。
  • unique_ptr类型为父类,管理的子类指针,析构时,可以正确调用子类的析构函数(就算父类析构函数不是虚函数也可以)。因为构造unique_ptr时,它已经记下了指针的具体类型,例如:unique_ptr<Base> b(new Derive);
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值