智能指针到底 “智能” 在哪里?

指针源自 C 语言的概念,本质上是一个内存地址索引,代表了一小片内存区域(也可能会很大),能够直接读写内存。因为它完全映射了计算机硬件,所以操作效率高,是 C / C++ 高效的根源。

C++ 里的垃圾回收并非 Java、Go 那种严格意义上的回收,而是广义上的垃圾回收,即构造 / 析构函数和 RAII (Resource Acquisition Is Initialization)惯用法。我们可以应用 代理模式 把裸指针包装起来,在构造函数里初始化,在析构函数里释放。这样当对象失效销毁时,C++ 就会自动调用析构函数,完成内存释放、资源回收等清理工作。智能指针完全实践了 RAII,包装了裸指针,而且因为重载了 * 和 -> 操作符,用起来和原始指针一模一样。

RAII(Resource Acquisition Is Initialization):资源的有效期与持有资源的对象的生命期严格绑定,即由资源的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能够正确地析构就不会出现资源泄漏的问题。

RAII - 维基百科,自由的百科全书

代理模式 - 维基百科,自由的百科全书

unique_ptr 是最简单、最容易使用的一个智能指针,在声明的时候必须用模板参数指明类型:

unique_ptr<int> ptr1(new int(10));
assert(*ptrl == 10);
assert(ptrl != nullptr);

unique_ptr<string> ptr2(new string("Hello World!"));
assert(*ptr2 == "Hello World!");
assert(ptr2->size() == 5);

unique_ptr 虽然名字叫指针,用起来也很像,但它实际上并不是指针,而是一个对象。所以,不要企图对它调用 delete,它会自动管理初始化时的指针,在离开作用域时析构释放内存。另外,它也没有定义加减运算,不能随意移动指针地址,这就完全避免了指针越界等危险操作。

ptr1 ++;    // 编译错误
ptr2 += 1;  // 编译错误

初学智能指针还有一个容易犯的错误是把它们当成普通对象来用,不初始化,而是声明后直接使用。

unique_ptr<int> ptr3;   // 未初始化智能指针
*ptr3 = 42;             // 错误!操作空指针