智能指针(现代 C++)

在现代 C++ 编程中,标准库包含智能指针,该指针用于确保程序不存在内存和资源泄漏且是异常安全的

智能指针的使用

智能指针定义在 <memory> 头文件中的 std 命名空间中。 它们对于 资源获取是初始化(RAII) 编程成语至关重要。 此成语的主要目标是确保资源获取在初始化对象的同时进行。 对象的所有资源都以一行代码创建并准备就绪。

实际上,RAII 的主要原则是将任何堆分配的资源所有权分配给堆栈分配的对象,其析构函数包含用于删除或释放资源的代码以及任何关联的清理代码。 此类对象包括动态分配的内存或系统对象句柄。

大多数情况下,当初始化原始指针或资源句柄以指向实际资源时,会立即将指针传递给智能指针。 在现代 C++ 中,原始指针仅用于范围有限、循环或帮助程序函数的小型代码块中,其中性能至关重要,并且无法混淆所有权。

下面的示例将原始指针声明与智能指针声明进行了比较。

void UseRawPointer()
{
    // Using a raw pointer -- not recommended.
    Song* pSong = new Song(L"Nothing on You", L"Bruno Mars"); 

    // Use pSong...

    // Don't forget to delete!
    delete pSong;   
}

void UseSmartPointer()
{
    // Declare a smart pointer on stack and pass it the raw pointer.
    unique_ptr<Song> song2(new Song(L"Nothing on You", L"Bruno Mars"));

    // Use song2...
    wstring s = song2->duration_;
    //...

} // song2 is deleted automatically here.

如示例所示,智能指针是你在堆栈上声明的类模板,并可通过使用指向某个堆分配的对象的原始指针进行初始化。 在初始化智能指针后,它将拥有原始的指针。 此方法意味着智能指针负责删除原始指针指定的内存。

智能指针的析构函数包含对 delete 的调用。 由于智能指针在堆栈上声明,因此当智能指针超出范围时会调用其析构函数。 即使在调用栈更上层的某处抛出了异常,它也会被调用。

使用熟悉的指针运算符访问封装的指针: ->*。 智能指针类重载这些运算符以返回封装的原始指针。

C++ 智能指针成语类似于使用 C# 等语言创建对象。 创建对象,然后让系统在正确的时间将其删除。 区别在于,后台没有单独的垃圾回收器运行。 内存通过标准 C++ 范围规则进行管理,以便运行时环境更快、更高效。

重要

始终在单独的代码行上创建智能指针,永远不会在参数列表中创建。 此方法可防止由于某些参数列表分配规则而导致微妙的资源泄漏。

下面的示例演示了如何使用 C++ 标准库中的 unique_ptr 智能指针类型将指针封装到大型对象。

class LargeObject
{
public:
    void DoSomething(){}
};

void ProcessLargeObject(const LargeObject& lo){}
void SmartPointerDemo()
{    
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass a reference to a method.
    ProcessLargeObject(*pLarge);

} //pLarge is deleted automatically when function block goes out of scope.

此示例演示如何使用智能指针执行以下关键步骤。

  1. 将智能指针声明为一个自动(局部)变量。 不要在智能指针本身上使用 newmalloc 表达式。

  2. 在类型参数中,指定封装指针的指向类型。

  3. 在智能指针构造函数中将原始指针传递至 new 对象。 某些实用工具函数或智能指针构造函数会为你执行此操作。

  4. 使用重载的 ->* 运算符访问对象。

  5. 允许智能指针删除对象。

智能指针的设计原则是在内存和性能上尽可能高效。 例如,unique_ptr 中的唯一数据成员是封装的指针。 这一事实意味着该 unique_ptr 指针的大小与该指针完全相同,可以是四个字节或八个字节。 通过智能指针重载的 *-> 运算符访问封装的指针,并不会明显慢于直接访问裸指针。

智能指针具有其自己的成员函数,这些函数是使用点表示法访问的。 例如,某些 C++ 标准库智能指针具有释放 reset 指针所有权的成员函数。 当你希望在智能指针离开作用域之前释放其管理的内存时,这一点非常有用,如下例所示。

void SmartPointerDemo2()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Free the memory before we exit function block.
    pLarge.reset();

    // Do some other work...

}

智能指针通常提供直接访问其原始指针的方法。 C++ 标准库智能指针具有 get 用于此目的的成员函数。 CComPtr 具有公共 p 类成员。 通过提供对基础指针的直接访问,可以使用智能指针管理你自己的代码中的内存,并将原始指针传递给不支持智能指针的代码。

void SmartPointerDemo4()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass raw pointer to a legacy API
    LegacyLargeObjectFunction(pLarge.get());    
}

智能指针的类型

下一节总结了 Windows 编程环境中可用的不同类型的智能指针,并说明了何时使用它们。

C++ 标准库智能指针

使用这些智能指针作为将指针封装为纯旧 C++ 对象 (POCO) 的首选项。

  • unique_ptr

    只允许底层指针有且仅有一个所有者。 对于 POCO,除非你明确知道自己需要 shared_ptr,否则请将其作为默认选择。 可以移到新所有者,但不会复制或共享。 替换已弃用的 auto_ptr。 与 boost::scoped_ptr 比较。

    unique_ptr 是小而高效的。 其大小相当于一个指针,并且支持右值引用,以便在 C++ 标准库集合中快速插入和从中检索。 头文件:<memory>。 有关详细信息,请参阅 如何:创建和使用unique_ptr实例unique_ptr类

  • shared_ptr

    采用引用计数的智能指针。 当想要向多个所有者分配一个原始指针时使用。 例如,可以从容器中返回指针的副本,但也可以保留原始副本。 只有当所有 shared_ptr 所有者都离开作用域或以其他方式放弃所有权后,才会删除该原始指针。

    其大小相当于两个指针的大小:一个指向对象,另一个指向包含引用计数的共享控制块。 头文件:<memory>。 有关详细信息,请参阅 “如何:创建和使用shared_ptr实例shared_ptr类

  • weak_ptr

    专用于 shared_ptr 的特殊智能指针。 weak_ptr 可访问一个由一个或多个 shared_ptr 实例拥有的对象,但不参与引用计数。 在想要观察对象时使用,但不要求它保持活动状态。 在某些情况下,需要断开 shared_ptr 实例间的循环引用。

    头文件:<memory>。 有关详细信息,请参阅 “如何:创建和使用weak_ptr实例weak_ptr类

COM 对象的智能指针(经典 Windows 编程)

当你使用 COM 对象时,请将接口指针包装到适当的智能指针类型中。 活动模板库 (ATL) 针对各种目的定义了多种智能指针。 你还可以使用 _com_ptr_t 智能指针类型,编译器在从 .tlb 文件创建包装器类时会使用该类型。 如果不想包含 ATL 头文件,这是最佳选择。

用于 POCO 对象的 ATL 智能指针

除 COM 对象的智能指针外,ATL 还为纯旧 C++ 对象 (POCO) 定义了智能指针和智能指针集合。 在经典Windows编程中,这些类型是 C++ 标准库集合的有用替代方法,尤其是在不需要代码可移植性或不想混合 C++ 标准库和 ATL 的编程模型时。

  • CAutoPtr 类

    通过在复制时转移所有权来确保唯一所有权的智能指针。 等同于已弃用的 std::auto_ptr 类。

  • CHeapPtr 类

    使用 C malloc 函数分配的对象的智能指针。

  • CAutoVectorPtr 类

    使用 new[] 分配的数组的智能指针。

  • CAutoPtrArray 类

    封装一个 CAutoPtr 元素数组的类。

  • CAutoPtrList 类

    封装用于操作 CAutoPtr 节点列表的方法的类。

另请参阅