一、概念介绍
unique_ptr它是一种在异常发生时可帮助避免资源泄露的smart pointer,实现了独占式拥有的概念,意味着它可确保一个对象和其他相应资源在同一时间只被一个pointer拥有,一旦拥有者被销毁或变成空或开始拥有另一个对象,那么先前拥有的那个对象就会被销毁,其任何相应资源亦会被释放。
Class unique_pt继承自class auto_ptr(由于不安全已被弃用),但它提供了更简明的接口,更不易出错。
1.1 出现的目的性
为了避免异常时的资源泄露,通常函数会捕获所有的异常,例如:
上图中多个catch时会造成代码臃肿且累赘,使用unique_ptr能很好解决这个问题,改写如下:
1.2 unique_ptr的使用
unique_ptr有着和寻常指针非常相似的接口,*操作符用来提取指向的对象,如果被指向的对象是class或struct,->操作符还可以用来访问成员,然而由于它是独占式的指针,所以不提供++算术符
1 //必须直接初始化,不允许用赋值的语法将一个寻常的指针当作初值 2 //std::unique_ptr<std::string> = new int;//error 3 std::unique_ptr<std::string> up(new std::string("nico"));//OK 4 (*up)[0] = 'X'; //替换第一个元素 5 (*up).append("lai"); 6 std::cout <<(*up).data() << std::endl; 7 std::cout << up.get()<<std::endl;
unique_ptr不必一定拥有对象,它也可以是empty,比如是被default构造函数创建出来的:
也可以使用release放弃拥有权,并转移到另外一个上:
//必须直接初始化,不允许用赋值的语法将一个寻常的指针当作初值 //std::unique_ptr<std::string> = new int;//error std::unique_ptr<std::string> up(new std::string("nico"));//OK (*up)[0] = 'X'; //替换第一个元素 (*up).append("lai"); std::cout <<(*up).data() << std::endl; std::cout << up.get()<<std::endl; auto str = up.release(); //release之后所有权就转移到str了,up就为NULL了 std::cout << str->data();
1.3 转移unique_ptr的拥有权
unique_ptr提供的语义是“独占式拥有”,然而这个责任必须由我们使用者来确保“不可以使两个unique_ptr以同一个pointer作为初值”,如下:
std::string *sp = new std::string("hello"); std::unique_ptr<std::string> up1(sp); std::unique_ptr<std::string> up2(sp);//错误,这是个运行期错误,
27行第一个语句结束后,up1拥有以new创建的对象,第二个语句试图调用copy构造函数,但发生编译期错误,因为up2不可以成为原对象的另一个拥有者,毕竟一次只允许存在一个拥有者,第三个语句则是将拥有权从up1转移到up3,在那之前up3就拥有了先前new建立起来的对象,而up1不再拥有它,一旦up3被销毁,以new创建出来的对象也就被delete,失去拥有权的unique_ptr会变为空,如果要想指派新值,新值必须也是个unique_ptr,不可以是寻常的pointer
std::unique_ptr<std::string> ptr; //ptr = new std::string; //error ptr = std::unique_ptr<std::string>(new std::string("hello"));//OK 删除就对象,拥有新对象(delete old object and own new)
ptr = nullptr;//和调用reset差不多,如果有对象的话删除关联的所有对象( deletes the associated,if any)
1.4 转移权unique_ptr两种用途
拥有权的转移指出了unique_ptr的一种用途:函数可以利用他们将拥有权转移给其他函数
- (1)、函数是接收端
如果我们将一个由std::move()建立起来的unique_ptr以rvalue reference身份当作函数实参,那么被调用函数的参数将会取得unique_ptr的拥有权,因此如果该函数不再转移拥有权,对象会在函数结束时被delete:
- (2)、函数是供应端
当函数返回一个unique_ptr,其拥有权会转移到调用端内:
注意:千万不要声明返回类型为rvalue reference,因为那会返回一个dangling pointer(悬空指针)
1.5 unique_ptr被当作成员可避免资源泄露
1.6 unique_ptr对于array的使用
1.7 标准库unique_ptr的默认形式
1.8 自定义deleter
当我们在unique_ptr结束时不再紧紧是调用delete或delete []时,我们就需要自定义deleter,然而此处的deleter定义方式不同于shared_ptr,你必须具体指明unique_ptr的类型的第二个模板参数,该类型可以时reference to function、function pointer或function object,如果是function object,其函数类型操作符()应该接受一个"指向对象"的pointer
1 //自定义deleter 2 class ClassDeleter 3 { 4 public: 5 void operator()(std::string *p) 6 { 7 std::cout << "delete string object" << std::endl; 8 delete p; 9 } 10 }; 11 12 13 std::unique_ptr<std::string, ClassDeleter> up(new std::string("hello"));
如果给入的类型是个函数或lambda,你必须声明deletr的类型为void(*)(T*)或std::function<void(T*)>,要不就使用decltype
1.9 unique_ptr使用举例
1 #include <iostream> 2 #include <string> 3 #include <memory> // for unique_ptr 4 #include <dirent.h> // for opendir(), ... 5 #include <cstring> // for strerror() 6 #include <cerrno> // for errno 7 using namespace std; 8 9 class DirCloser 10 { 11 public: 12 void operator () (DIR* dp) { 13 if (closedir(dp) != 0) { 14 std::cerr << "OOPS: closedir() failed" << std::endl; 15 } 16 } 17 }; 18 19 int main() 20 { 21 // open current directory: 22 unique_ptr<DIR,DirCloser> pDir(opendir(".")); 23 24 // process each directory entry: 25 struct dirent *dp; 26 while ((dp = readdir(pDir.get())) != nullptr) { 27 string filename(dp->d_name); 28 cout << "process " << filename << endl; 29 //... 30 } 31 }
上例中使用了标准posix接口:opendir()、readdir()、closedir(),为了确保任何情况下被开启的目录都会被closedir()关闭,我们定义一个unique_ptr,造成只要”指向被开启目录“的那个handle被销毁,DirCloser就会被调用。
1.10 unique_ptr深究
class unique_ptr提供了一个”带有独占拥有权的smart pointer语义“的概念,一旦某个unique pointer有了独占控制权,你将无法创造出”多个pointer拥有该相应对象“的形势,主要目的是确保在那个pointer寿命结束时,相应对象被删除(或是资源被清理干净),这特别有助于提供异常安全性,相比于shared pointer,这个class关注的是最小量的空间开销和时间开销。