简介在现代 C 编程中标准库包含了智能指针(Smart pointers)。智能指针用来确保程序不会出现内存和资源的泄漏并且是异常安全(exception-safe)的。智能指针的使用智能指针定义在头文件 memory 里的命名空间 std 中。它对于资源获取即初始化(RAII, Resource Acquisition Is Initialization) 编程理念至关重要。该理念的目的是保证对象初始化的时候也是资源获取的时候从而使对象的所有资源在单行代码中创建。实践中RAII 的主要原则就是把任何在堆上分配的资源(比如动态分配的内存或者系统对象的处理)的所有权提供给在栈上分配的对象(其析构函数包含释放资源及相关清理的代码)。大多数时候当你初始化一个原始指针或者资源句柄使其指向实际的资源时立即将其传给智能指针。在现代 C 中原始指针只用于包含在局部作用域循环或者工具函数的小块代码中(对性能有要求并且对资源的所有权也不容易混淆)。原始指针和智能指针的声明比较如下123456789101112131415161718192021voidUseRawPointer(){// Using a raw pointer -- not recommended.Song* pSong newSong(LNothing on You, LBruno Mars);// Use pSong...// Dont forget to delete!deletepSong;}voidUseSmartPointer(){// Declare a smart pointer on stack and pass it the raw pointer.unique_ptrSong song2(newSong(LNothing on You, LBruno Mars));// Use song2...wstring s song2-duration_;//...}// song2 is deleted automatically here.如上所示智能指针是一个在栈上声明的类模板并由指向分配在堆上的对象的原始指针初始化。当智能指针初始化后它就拥有了原始指针的所有权。这意味着智能指针需要负责原始指针指向的内存释放。智能指针的析构函数包含了 delete 的调用并且由于智能指针是在栈上声明的其析构函数会在智能指针对象离开作用域时被调用即使在栈中发生了异常。通过使用指针运算符(- 和 *)访问被封装的指针智能指针类重载了这些运算符以返回被封装的原始指针。C 智能指针的理念类似于在 C# 语言中创建对象的过程创建对象后让系统负责在正确的时间将其删除。不同之处在于没有独立的垃圾回收器运行于后台内存是按照标准 C 规范对内存进行管理的使运行时环境更加快速和高效。[!重要]总是在单独的行上创建智能指针而不是在参数列表中从而避免由于特定的参数列表分配规则出现一些轻微的内存泄漏以下示例显示了 C 标准库中的 unique_ptr 是如何封装指向大型对象的指针的。1234567891011121314151617181920classLargeObject{public:voidDoSomething(){}};voidProcessLargeObject(constLargeObject lo){}voidSmartPointerDemo(){// Create the object and pass it to a smart pointerstd::unique_ptrLargeObject pLarge(newLargeObject());//Call a method on the objectpLarge-DoSomething();// Pass a reference to a method.ProcessLargeObject(*pLarge);}//pLarge is deleted automatically when function block goes out of scope.上述示例演示了使用智能指针的关键步骤将智能指针声明为局部变量(不要在智能指针上使用 new 或者 malloc 表达式)。在类型参数上指定被封装指针指向的对象类型。将指向由 new 创建的对象的指针传给智能指针的构造函数。使用重载的操作符 - 和 * 来访问对象。让智能指针来 delete 对象。智能指针在设计上兼顾了内存和性能的高效性。例如unique_ptr 唯一的数据成员是被封装的原始指针这意味着 unique_ptr 具有原始指针同样地大小4 字节或者 8 字节。通过智能指针重载的操作符 - 和 * 来访问并不比直接使用原始指针来访问慢多少。智能指针有其自己的成员函数通过 . 来访问。例如一些 C 标准库的智能指针有用于重置的成员函数来释放对原始指针的所有权。这可以用于在智能指针超出作用域前释放智能指针管理的内存看下面的示例1234567891011121314voidSmartPointerDemo2(){// Create the object and pass it to a smart pointerstd::unique_ptrLargeObject pLarge(newLargeObject());//Call a method on the objectpLarge-DoSomething();// Free the memory before we exit function block.pLarge.reset();// Do some other work...}智能指针通常提供了获取原始指针的方式。 C 标准库中的智能指针包含了成员函数 get 来获取原始指针。 CComPtr 有公共的类成员 p。通过获取原始指针你能够使用智能指针来管理你自己代码涉及的内存并依然能够将原始指针传递给不支持智能指针的代码。1234567891011voidSmartPointerDemo4(){// Create the object and pass it to a smart pointerstd::unique_ptrLargeObject pLarge(newLargeObject());//Call a method on the objectpLarge-DoSomething();// Pass raw pointer to a legacy APILegacyLargeObjectFunction(pLarge.get());}智能指针的种类以下部分总结了在 Windows 环境下不同种类的智能指针以及如何使用它们。C 标准库中的智能指针优先使用下列智能指针来封装原始指针指向的纯旧对象(plain old C objectsPOCO):unique_ptr对封装的原始指针是独占的默认用于 POCO除非你明确的知道你需要一个 shared_ptr可以移入新的所有者但不能拷贝或者共享替代 auto_ptrauto_ptr 已作废对比 boost::scoped_ptrunique_ptr 更加小巧和高效长度为一个指针的大小并且支持右值引用来快速执行 C 标准库容器的插入和遍历操作shared_ptr引用计数智能指针当你需要将原始指针分派给多个所有者时使用例如当你从容器返回一个指针的拷贝并且想要保留它原始指针不会被 delete 直到所有的 shared_ptr 超出作用域或者放弃所有权。长度为两个指针的大小一个用于对象另一个用于包含引用计数的共享控制块weak_ptr结合 shared_ptr 使用的特殊智能指针。weak_ptr 提供了对被一个或者多个 shared_ptr 所拥有的对象的访问但不参与引用计数。如果你想要监测某个对象不要求其不被释放可以使用 weak_ptr在某些情况下用于解决 shared_ptr 实例间的循环引用。