C++11新引入了几种智能指针:unique_ptr
,shared_ptr
和weak_ptr
,而原来的auto_ptr
被弃用。
我会写几篇文章分别来介绍这几种智能指针的用法,本篇主要介绍unique_ptr
。
主要介绍unique_ptr
的两个主要特性:
- 保存对象的指针,当
unique_ptr
本身释放的时候,自动调用对象的析构函数。
- 唯一拥有它指向的对象,无法通过拷贝构造或者等号进行赋值。
我们先定义一个简单的类作为示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
#include <iostream>
class Test { public: Test(int tag) : tag(tag) { std::cout << "Test::Test(int) " << tag << std::endl; } ~Test() { std::cout << "Test::~Test() " << tag << std::endl; } void test() { std::cout << "Test::test() " << tag << std::endl; }
private: int tag; };
|
特性1 - 保存对象的指针,当unique_ptr
本身释放的时候,自动调用对象的析构函数
这是一个智能指针的本分,让我们免去烦人又容易出错的new/delete
操作。
示例1 - 最简单场景
1 2 3 4 5 6 7 8 9 10 11
|
#include <iostream> #include <memory> #include "test.h"
int main() { std::unique_ptr<Test> p(new Test(1)); p->test(); return 0; }
|
编译并执行
1 2
| g++ -o example1 -std=c++11 example1.cpp ./example1
|
输出结果
1 2 3
| Test::Test(int) 1 Test::test() 1 Test::~Test() 1
|
基本不需要解释,我们看到我们并没有调用delete但是Test的析构函数还是被调用了。
示例2 - 有异常的场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
#include <iostream> #include <memory> #include <string> #include "test.h"
int test(int tag) { std::unique_ptr<Test> p(new Test(tag)); if (tag / 2) { throw "except, tag = " + std::to_string(tag); } return tag; }
int main() { try { int ret = test(1); std::cout << "test(1) return " << ret << std::endl; ret = test(2); std::cout << "test(2) return " << ret << std::endl; } catch (std::string e) { std::cout << "exception caught: " << e << std::endl; } return 0; }
|
编译并执行
1 2
| g++ -o example2 -std=c++11 example2.cpp ./example2
|
输出结果
1 2 3 4 5 6
| Test::Test(int) 1 Test::~Test() 1 test(1) return 1 Test::Test(int) 2 Test::~Test() 2 exception caught: except, tag = 2
|
第一次调用test(1)
的时候,没有异常抛出,函数正常返回,我们看到函数返回前,Test
的析构函数得到了调用。
第二次调用test(2)
的时候,函数抛出了异常,要是普通指针的话,因为函数并没有正常结束,异常之后的语句就不再被调用,包括delete
语句,就造成了内存泄漏。然而本例中我们看到即使异常抛出,Test
的析构函数还是得到了调用,这就是智能指针的功劳。
特性2 - 唯一拥有它指向的对象,无法通过拷贝构造或者等号进行赋值。
这个特性就是unique_ptr
独有的特性了。
理解这个特性,需要结合C++11新引入的move语义,move语义不在本文的讨论范围,以后有精力我可能会写一篇关于move语义的文章,现在你想了解move语义的话可以参考这几篇文章:
我们看一下下边的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
#include <iostream> #include <memory> #include "test.h"
void passTest(std::unique_ptr<Test> t) { t->test(); }
std::unique_ptr<Test> getPtr(int tag) { std::unique_ptr<Test> p(new Test(tag)); return p; }
int main() { std::unique_ptr<Test> p = std::unique_ptr<Test>(new Test(1)); p->test();
std::unique_ptr<Test> p1 = std::move(p); p1->test();
p = std::unique_ptr<Test>(new Test(2)); passTest(std::move(p)); passTest(std::unique_ptr<Test>(new Test(3)));
p = getPtr(4);
return 0; }
|
编译并执行
1 2
| g++ -o example3 -std=c++11 example3.cpp ./example3
|
输出结果
1 2 3 4 5 6 7 8 9 10
| Test::Test(int) 1 Test::test() 1 Test::test() 1 Test::Test(int) 2 Test::test() 2 Test::~Test() 2 Test::Test(int) 3 Test::test() 3 Test::~Test() 3 Test::~Test() 1
|
这个例子主要是展示了unique_ptr
的唯一性,也就是说unique_ptr
唯一持有它指向的对象,无法通过赋值(1)或者拷贝构造(2)的方式进行初始化,它只能接受右值语义的参数来构造(0)(3)。
(4)(5)展示了move
之后p
已经失效。
(6)(7)(8)则展示了作为函数参数传递,同样要满足右值语义才可以。
(9)展示了作为函数返回值给unique_ptr
赋值,这同样是满足右值语义的。
上边的这几个例子都说明了unique_ptr
的唯一性,我们可以理解成任意时刻,只要你持有一个合法的unique_ptr
,就可以保证你是唯一的一个持有人,不会出现另一个unique_ptr
跟你相同的情况。