缘起
对于C/C++程序员而言,资源管理是从来都不可回避的一个话题,资源泄露也成了程序员挥之不去的噩梦,稍不小心就掉到坑里去了,为了解决这些问题,托管语言引入了GC机制,由于C++缺乏语言层面的GC机制,资源管理一直很棘手,也是被很多人诟病。为了管理资源,C++采用RAII手法(资源获取即初始化,Resource Acquisition Is Initialization),利用局部对象管理资源,在使用资源的类的构造函数中申请资源,然后使用,最后在析构函数中释放资源。如果对象是用声明的方式在栈上创建的局部对象,那么RAII机制会正常工作,当离开作用域时对象会自动销毁从而调用析构函数释放资源。
对于动态内存的管理,C++通过类来封装和管理内存资源,就是我们所谓的智能指针,用于对象的生命周期管理,智能指针的用法和内建指针没有太大的区别,最重要的是它的效率并不弱于内建指针。C++98 只提供了唯一的严格的对象的所有权语义的智能指针auto_ptr,在C++ 11中被废弃了。C++11 引入了两个指针类: shared_ptr 和 unique_ptr。 shared_ptr只是单纯的引用计数指针,unique_ptr 是用来取代auto_ptr。 unique_ptr 提供 auto_ptr 大部份特性,唯一的例外是 auto_ptr 的不安全、隐性的左值搬移,自从auto_ptr存在以来,一直就争议不断,就是auto_ptr的破坏性复制语义,将一个auto_ptr复制给另一个之后,原来的auto_ptr就不指向任何东西,被重置为未绑定状态,出现所有权转移,因此,不能将auto_ptr对象存储在标准容器中,标准库的容器类要求在复制或赋值之后两个对象相等,auto_ptr并不满足这一要求。不像 auto_ptr,unique_ptr 可以存放在 C++11提出的那些能察觉搬移动作的容器之中。两者都兼容其它标准库组件,因此你可以在标准容器内安全保存这些智能指针,并使用标准算法操作它们。对于非内存资源的管理,如资源句柄,控制锁等等,在后面的文章会讲到。
关于智能指针的缘起,并不是学院派的产物,而是实际编码开发的结果,由于异常安全的问题愈来愈突出,严重影响到标准库的设计,智能指针才进入了C++委员会的视野,最早提交的解决方案是两个智能指针类(资源独占的auto_ptr和进行引用计数的counted_ptr)和GC机制,最后的结果是counted_ptr和GC双双落马,唯一幸存的auto_ptr,语义由独占所有权改为转移所有权。可以参见《智能指针的标准之争:Boost vs. Loki》和《再论智能指针的标准之争:Boost vs. Loki》。
分析
C/C++ 程序员需要反复面对这样的问题:在程序中处理内存的分配和释放。其困难众所周知:异常、函数中的多条返回路径,以及被调用者分配的、必须由调用者释放的内存——在面对这些情况时,要确保程序不泄漏资源可能会极其困难。这在多线程程序中尤其重要:如果你没有严格地跟踪动态内存的所有权,某个线程仍然在使用的内存可能会被其他线程删除,从而带来灾难性的后果。
智能指针的最初动机是使得下面的代码更安全
void foo()
{
Type* ptr = new Type[10];
// 对p指向的内存块进行赋值
do_something();
delete[] ptr;
}
在c++11中,引入了两个智能指针shared_ptr和 unique_ptr,在c++中,存在两种对应的模型:值模型和引用模型,提供给对象的值语义和对象语义,值语义表示对象的拷贝会得到一个不同的新副本 ,与原对象是独立的,对象语义下得拷贝是禁止的,只能通过引用和指针来操作,所指向的对象是共享的。值语义对象生命期管理容易,对象语义对象生命期却非常难以处理,智能指针实际上是将对象语义转化为值语义,利用局部对象(智能指针)的确定性析构,包括废弃的auto_ptr,c++11中加入的 shared_ptr, weak_ptr和unique_ptr。
unique_ptr
unique_ptr是拥有唯一的对象语义的智能指针,同一对象只能被一个unique_ptr来拥有,禁止进行拷贝构造和赋值构造操作,但是,依赖于C++11中的右值引用和move语义,它可以转让所有权给另一个unique_ptr,C++11提供的move语义可以将资源unique_ptr管理的对象转移到另一个对象,但是并不进行资源的拷贝操作,只是进行资源所有权的转移,这样能够减少不必要的临时对象的创建、拷贝以及销毁,效率得到了大大的提升,当unique_ptr指针对象离开其作用域时,生命期结束,自动使用内部给定的删除器(deleter)delete所指向的对象。我们知道,类对象的拷贝我们需要定义copy constructor和copy assignment,同样,要实现move语义,需要定义move constructor和move assignment。对于右值的拷贝和赋值会调用move constructor和move assignment。如果没有定义move constructor和move assignment,那么就遵循现有的机制, copy constructor和copy assignment 会被调用。
constexpr unique_ptr() noexcept;//默认构造
constexpr unique_ptr (nullptr_t) noexcept : unique_ptr() {}//从null pointer构造
unique_ptr& operator= (nullptr_t) noexcept;
explicit unique_ptr (pointer p) noexcept;//从pointer构造
unique_ptr (unique_ptr&& x) noexcept;//move构造
unique_ptr& operator= (unique_ptr&& x) noexcept;//move赋值
template <class U>
unique_ptr (auto_ptr<U>&& x) noexcept;//从auto_ptr指针move构造
unique_ptr (const unique_ptr&) = delete;//禁用拷贝构造函数
unique_ptr& operator= (const unique_ptr&) = delete;//禁用赋值操作
unique_ptr常见的用途有下面几点:
<1>为那些控制动态生命周期对象的类和函数提供异常安全,保证正常退出和异常退出时这些动态内存正常释放。
<2>将动态对象的唯一控制权转移给函数
<3>从函数获取动态对象唯一控制权
<4>可以做move-aware的容器的元素,如std::vector.如,用来装有多态性的动态对象指针。
如果你想把对象所有权转移给另一个unique_ptr,需要使用move语义。在所有权转移后,交出所有权的智能指针将为空,get()函数将返回nullptr。
void test_unique_ptr()
{
std::unique_ptr<int> up1 (new int);
std::unique_ptr<int> up2 (std::move(up1));//所有权转移,up2拥有所有权
up1 = std::move(up2);//所有权转移回up1
// std::unique_ptr<int> up4 (std::auto_ptr<int>(new int));
}
unique_ptr对于boost库中的scoped_ptr智能指针和C++98的auto_ptr做了很多的改进,auto_ptr在之前讲过,具有破坏性复制语义,支持拷贝构造和赋值构造操作,也可作为函数的返回值,scoped_ptr是独占所有权语义,对象语义,不能作为函数的返回值,unique_ptr也不支持拷贝构造和赋值构造,但是move语义的控制,可以很好的实现所有权的转移,可以作为函数的返回值
template<typename T>
std::unique_ptr<T> getMemory(T value)
{
std::unique_ptr<T> uniquePtr(new T(value));
return uniquePtr;
}
void test_unique_ptr()
{
std::unique_ptr<int> testUniqueptr = getMemory<int>(12);
}
unique_ptr作为函数参数和函数返回值,实现所有权的转移
template<typename T>
std::unique_ptr<T>& foo1(std::unique_ptr<T>& param)
{
return param;
}
template<typename T>
std::unique_ptr<T> foo2(std::unique_ptr<T> param)
{
return std::move(param);
}
template<typename T>
std::unique_ptr<T>&& foo3(std::unique_ptr<T>&& param)
{
return std::move(param);
}
int main()
{
std::unique_ptr<int> tempUniquePtr;
std::unique_ptr<int> uniquePtr1(new int(20));
tempUniquePtr = std::move(foo1<int>(uniquePtr1));
std::unique_ptr<int> uniquePtr2(new int(100));
tempUniquePtr = foo2<int>(std::move(uniquePtr2));
foo2(std::unique_ptr<int>(new int(4)));
std::unique_ptr<int> uniquePtr3(new int(5));
tempUniquePtr = foo3<int>(std::move(uniquePtr3));
return 0;
}
参考
http://zh.cppreference.com/w/cpp/memory/unique_ptr
http://www.cplusplus.com/reference/memory/unique_ptr/
http://zh.wikipedia.org/wiki/Memory_(C%2B%2B%E6%A0%87%E5%87%86%E5%BA%93)
http://zjulbj.github.io/stl/2013/08/20/c++%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88%E4%BB%8B%E7%BB%8D(%E4%BA%8C)%EF%BC%9Aunique_ptr/