智能指针之unique_ptr
前言
Java说:内存管理是如此重要,不能给程序员管理。
C++:内存管理是如此重要,必须由程序员管理。
内存管理技术分为自动内存管理(Java、Python等)和半自动内存管理(C++)。C++支持半自动内存管理的理论基础是值语义;技术基础是智能指针。
C++里面的四个智能指针: auto_ptr
, unique_ptr
,shared_ptr
, weak_ptr
其中后三个是C++11支持,并且第一个已经被C++11弃用。
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。
什么情况下使用unique_ptr
智能指针试图解决资源的所有权问题。
一个指针对资源所有权的要求可以分为三种情况:
- 不需要资源的所有权:使用
weak_ptr
或者裸指针 - 独占资源的所有权:
unique_ptr
- 共享资源的所有权:
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
析构管理指针时,调用改指针对象的默认析构函数。如果设置了deletor
,unique_ptr
将调用deletor
函数来析构管理指针。
设置方法
deletor
是一个执行体,接受一个参数,参数类型为_T*,deletor
可以是函数指针,lamda表达式或者仿函数。deletor
是unique_ptr
类型的一部分。同样指针类型,不同的deletor
的unique_ptr
类型不一样。
class A;
auto MyDeletor1 = [](A *a)
{
//do something
delete a;
};
unique_ptr<A, decltype(MyDeletor1)> p1(new A, MyDeletor1);
deletor对unique_ptr大小
不带deletor
的unique_ptr
大小与裸指针大小相同,带有deletor
的unique_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);