首页 > 编程笔记

C++智能指针的使用(非常详细)

在 C++ 编程中,如果使用 new 手动申请了内存,则必须要使用 delete 手动释放,否则会造成内存泄漏。

对内存管理来说,手动释放内存并不是一个好的解决方案,C++98 标准提供了智能指针 auto_ptr 解决了内存的自动释放问题,但是 auto_ptr 有诸多缺点,例如,不能调用 delete[],并且,如果 auto_ptr 出现错误,只能在运行时检测而无法在编译时检测。

为此,C++11 标准提出了三个新的智能指针:unique_ptrshared_ptr weak_ptr。unique_ptr、shared_ptr 和 weak_ptr 是 C++11 标准提供的模板类,用于管理 new 申请的堆内存空间。

这些模板类定义了一个以堆内存空间(new 申请的)指针为参数的构造函数,在创建智能指针对象时,将 new 返回的指针作为参数。同样,这些模板类也定义了析构函数,在析构函数中调用 delete 释放 new 申请的内存。当智能指针对象生命周期结束时,系统调用析构函数释放 new 申请的内存空间。本文将针对这三个智能指针进行讲解。

unique_ptr

unique_ptr 智能指针主要用来代替 C++98 标准中的 auto_ptr,它的使用方法与 auto_ptr 相同。

创建 unique_ptr 智能指针对象的语法格式如下所示:

unique_ptr<T>  智能指针对象名称(指针);

在上述格式中,unique_ptr<T> 是模板类型,后面是智能指针对象名称,遵守标识符命名规范。

智能指针对象名称后面的小括号中的参数是一个指针,该指针是 new 运算符申请堆内存空间返回的指针。

unique_ptr 智能指针的用法示例代码如下所示:
unique_ptr<int> pi(new int(10));
class A {…};
unique_ptr<A> pA(new A);
代码分析:
当程序运行结束时,即使没有 delete,编译器也会调用 unique_ptr 模板类的析构函数释放 new 申请的堆内存空间。需要注意的是,使用智能指针需要包含 memory头文件。

unique_ptr 智能指针对象之间不可以赋值,错误示例代码如下所示:
unique_ptr<string> ps(new string("C++"));
unique_ptr<string> pt;
pt = ps;   //错误,不能对unique_ptr智能指针赋值
在上述代码中,直接将智能指针 ps 赋值给智能指针 pt,编译器会报错。这是因为在 unique_ptr 模板类中,使用“=delete”修饰了“=”运算符的重载函数。

之所以这样做,是因为 unique_ptr 在实现时是通过所有权的方式管理 new 对象指针的,一个 new 对象指针只能被一个 unique_ptr 智能指针对象管理,即 unique_ptr智能指针拥有对 new 对象指针的所有权。

当发生赋值操作时,智能指针会转让所有权。例如,上述代码中的 pt=ps 语句,如果赋值成功,pt 将拥有对 new 对象指针的所有权,而 ps 则失去所有权,指向无效的数据,成了危险的悬挂指针。如果后面程序中使用到 ps,会造成程序崩溃。

C++98 标准中的 auto_ptr 就是这种实现方式,因此 auto_ptr 使用起来比较危险。C++11 标准为了修复这种缺陷,就将 unique_ptr 限制为不能直接使用=进行赋值。

如果需要实现 unique_ptr 智能指针对象之间的赋值,可以调用 C++ 标准库提供的 move() 函数,示例代码如下所示:
unique_ptr<string> ps(new string("C++"));
unique_ptr<string> pt;
pt = move(ps);   //正确,可以通过编译
调用 move() 函数完成赋值之后,pt 拥有 new 对象指针的所有权,而 ps 则被赋值为 nullptr。

shared_ptr

shared_ptr 是一种智能级别更高的指针,它在实现时采用了引用计数的方式,多个 shared_ptr 智能指针对象可以同时管理一个 new 对象指针。

每增加一个 shared_ptr 智能指针对象,new 对象指针的引用计数就加 1;当 shared_ptr 智能指针对象失效时,new 对象指针的引用计数就减 1,而其他 shared_ptr 智能指针对象的使用并不会受到影响。只有在引用计数归为 0 时,shared_ptr 才会真正释放所管理的堆内存空间。

shared_ptr 与 unique_ptr 用法相同,创建 shared_ptr 智能指针对象的格式如下所示:

shared_ptr<T>  智能指针对象名称(指针);


shared_ptr 提供了一些成员函数以更方便地管理堆内存空间,下面介绍几个常用的成员函数。

1) get()函数

用于获取 shared_ptr 管理的 new 对象指针,函数声明如下所示:
T* get() const;
在上述函数声明中,get() 函数返回一个 T* 类型的指针。当使用 cout 输出 get() 函数的返回结果时,会得到 new 对象的地址。

2) use_count()函数

用于获取 new 对象的引用计数,函数声明如下所示:
long use_count() const;
在上述函数声明中,use_count() 函数返回一个 long 类型的数据,表示 new 对象的引用计数。

3) reset()函数

用于取消 shared_ptr 智能指针对象对 new 对象的引用,函数声明如下所示:
void reset();
在上述函数声明中,reset() 的声明比较简单,既没有参数也没有返回值。当调用 reset() 函数之后,new 对象的引用计数就会减 1。取消引用之后,当前智能指针对象被赋值为nullptr

【示例1】下面通过案例演示 shared_ptr 智能指针的使用,C++ 代码如下:
#include<iostream>
#include<memory>
using namespace std;
int main()
{
    //创建shared_ptr智能指针对象language1、language2、language3
    shared_ptr<string> language1(new string("C++"));
    shared_ptr<string> language2 = language1;
    shared_ptr<string> language3 = language1;
    //通过智能指针对象language1、language2、language3调用get()函数
    cout << "language1: " << language1.get() << endl;
    cout << "language2: " << language2.get() << endl;
    cout << "language3: " << language3.get() << endl;
    cout << "引用计数:";
    cout << language1.use_count() <<" ";
    cout << language2.use_count() <<" ";
    cout << language3.use_count() <<endl;
    language1.reset();
    cout << "引用计数:";
    cout << language1.use_count()<<" ";
    cout << language2.use_count()<<" ";
    cout << language3.use_count() << endl;
    cout << "language1: " << language1.get() << endl;
    cout << "language2: " << language2.get() << endl;
    cout << "language3: " << language3.get() << endl;
    return 0;
}
运行结果:

language1: 0x1410e70
language2: 0x1410e70
language3: 0x1410e70
引用计数:3 3 3
引用计数:0 2 2
language1: 0
language2: 0x1410e70
language3: 0x1410e70

示例分析:
由运行结果可知,language2 和 language3 仍旧指向地址为 0x1410e70 的堆内存空间,而 language1 指向 00000000,即 nullptr 的值。

weak_ptr

相比于 unique_ptr 与 shared_ptr,weak_ptr 智能指针的使用更复杂一些,它可以指向 shared_ptr 管理的 new 对象,却没有该对象的所有权,即无法通过 weak_ptr 对象管理 new 对象。

图1   shared_ptr、weak_ptr 和 new 对象的关系示意图

图1   shared_ptr、weak_ptr 和 new 对象的关系示意图

在图 1 中,shared_ptr 对象和 weak_ptr 对象指向同一个 new 对象,但 weak_ptr 对象却不具有 new 对象的所有权。

weak_ptr 模板类没有提供与 unique_ptr、shared_ptr 相同的构造函数,因此,不能通过传递 new 对象指针的方式创建 weak_ptr 对象。weak_ptr 最常见的用法是验证 shared_ptr 对象的有效性。

weak_ptr 提供了一个成员函数 lock(),该函数用于返回一个 shared_ptr 对象,如果 weak_ptr 指向的 new 对象没有 shared_ptr 引用,则 lock() 函数返回 nullptr。

示例

下面通过案例演示 weak_ptr 智能指针的使用,C++ 代码如下:
#include<iostream>
#include<memory>
using namespace std;
void func(weak_ptr<string>& pw)
{
    //通过pw.lock()获取一个shared_ptr对象
    shared_ptr<string> ps = pw.lock();
    if(ps != nullptr)
        cout << "编程语言是" << *ps << endl;
    else
        cout << "shared_ptr智能指针失效!" << endl;
}
int main()
{
    //定义shared_ptr对象pt1与pt2
    shared_ptr<string> pt1(new string("C++"));
    shared_ptr<string> pt2 = pt1;
    //定义weak_ptr对象
    weak_ptr<string> pw = pt1;
    func(pw);   //调用func()函数
    *pt1 = "Java";
    pt1.reset();   //取消pt1的引用
    func(pw);   //调用func()函数
    pt2.reset();   //取消pt2的引用
    func(pw);   //调用func()函数
    return 0;
}
运行结果:

编程语言是C++
编程语言是Java
shared_ptr智能指针失效!

示例分析:
本例可以通过 pt1 修改 new 对象中的数据,但是,由于 weak_ptr 对 new 对象没有所有权,因此无法通过 pw 修改 new 对象中的数据。

优秀文章