C++11:unique_ptr、shared_ptr、weak_ptr,本节介绍一下unique_ptr
1.智能指针产生原因
众所周知,C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。然而,很多程序员在使用堆内存时都很头疼,因为一次不恰当的使用方法,很容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等。所以C++引入了智能指针,使用使用智能指针能更好的管理堆内存。
2.理解智能指针原理
1)智能指针是利用了一种叫做RAII技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。
补充:RAII技术即资源分配及初始化,使用类来封装资源的分配和初始化,构造函数完成资源的分配和初始化,析构函数完成资源的清理,可以保证正确的初始化和资源释放。
2)智能指针是一个类,这个类的构造函数中传入一个普通指针,析构函数中释放传入的指针。智能指针的类都是栈上的对象,所以当函数(或程序)结束时会自动被释放。智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决。千万不要用一块非new分配的动态内存去初始化一个智能指针,因为自行释放非堆地址很有可能出现问题。
3)智能指针还有一个作用是把值语义转换成引用语义。
C++和Java有一处最大的区别在于语义不同,在Java里面,下列代码:
Student s1 = new Student ();
Student s2 = s1;
这里其实只生成了一个对象,s1和s2仅仅是共享对象的引用而已。但在C++中不是这样的,
Student s1;
Student s2 = s1;
这里却是就是生成了两个对象。
3.包含头文件
C++98版的auto_ptr和C++11版的unique_ptr、shared_ptr、weak_ptr,都包含在头文件<memory>中,隶属于std命名空间中。
4.unique_ptr
unique_ptr持有对对象的独有权——两个unique_ptr不能指向一个对象,不能进行复制操作只能进行移动操作。但支持通过std::move转移内部指针所有权。
unique_ptr的成员函数:
1)get 获得内部对象的指针, 由于已经重载了()方法, 因此和直接使用对象是一样的.如 unique_ptr<int> sp(new int(1)); sp 与 sp.get()是等价的;
2)release 放弃内部对象的所有权,将内部指针置为空, 返回所内部对象的指针, 此指针需要手动释放;
3)reset 销毁内部对象并接受新的对象的所有权(如果使用缺省参数的话,也就是没有任何对象的所有权, 此时仅将内部对象释放, 并置为空);
unique_ptr<int> up1(new int); // ok
up1 = nullptr; //显式销毁所指对象,同时智能指针变为空指针。与u_s2.reset()等价
4)swap 交换两个unique_ptr对象(即交换所拥有的对象);
unique_ptr的用法:
1)unique_ptr对象不能进行拷贝和赋值操作,只能进行移动操作
unique_ptr<int> up1(new int); // ok
unique_ptr<int> up2 = up1; // 编译错误
unique_ptr<int> up3(up1); // 编译错误
unique_ptr<int> up4 = move(up1); // ok,up1变成空指针
查看拷贝构造和赋值源码,可以看到它们都被标记为已删除的函数了,在编译上面的代码时,会报编译错误:
unique_ptr(const _Myt&) = delete;
_Myt& operator=(const _Myt&) = delete;
当然,有些博主解释为,它们被设成了不可访问的私有函数。
虽然 unique_ptr 不支持拷贝和赋值, 但是可以调用 release 或 reset 将指针的所有权从一个(非 const) unique_ptr 转移到另一个。
std::unique_ptr<int> up1(new int(1));
std::unique_ptr<int> up2(up1.release()); // up1 放弃内部对象的所有权,将内部指针置为空
std::unique_ptr<int> up3(new int(2));
up3.reset(up2.release()); //up3销毁内部对象,并接受新的对象的所有权
2)源unique_ptr是临时右值时,编译器允许将源unique_ptr临时右值赋给另一个unique_ptr对象
// 可以进行移动构造
template <class Ty>
std::unique_ptr<Ty> Clone(const Ty& obj)
{
std::unique_ptr<Ty> temp = std::unique_ptr<Ty>(new Ty(obj)); // 编译器允许,源右值对象是临时右值
return temp; // 对象的所有权被传递出Clone()
}
unique_ptr<int> pu1 = Clone(5);// 可以进行移动赋值,Clone函数返回值也是临时右值
unique_ptr<int> pu2;
pu2 = pu1; // pu1不是临时右值,编译器不允许
unique_ptr<int> pu3;
pu3 = unique_ptr<int>(new int(2)); // 编译器允许,源右值对象是临右值
应该是编译器知道要临时右值的unique_ptr对象即将被销毁, 因此执行一种特殊的"拷贝"。
3)两个unique_ptr不能指向一个对象
int *iPtr = new int(0);
std::unique_ptr<int> up1(iPtr);
std::unique_ptr<int> up2(iPtr);// 编译可以通过,但运行时发生错误
up2和up1都指向了iPtr,将来iPtr会被释放两次,造成错误。
4)unique_ptr支持管理动态数组
因为unique_ptr有unique_ptr<X[]>重载版本,销毁动态对象时调用delete[]
unique_ptr<int[]> p (new int[3]{1,2,3});
p[0] = 0;// 重载了operator[]
5)可以使用unique_ptr的移动语义将对象插入容器中
unique_ptr<int> pu1(new int(1));
vector<unique_ptr<int> > vect;
vect.push_back(pu1); // 编译错误
vect.push_back(std::move(pu1)); // OK,使用移动语义,pu1自己就不把持对象所有权了