C++智能指针的使用(非常详细)
在 C++ 编程中,如果使用 new 手动申请了内存,则必须要使用 delete 手动释放,否则会造成内存泄漏。
对内存管理来说,手动释放内存并不是一个好的解决方案,C++98 标准提供了智能指针 auto_ptr 解决了内存的自动释放问题,但是 auto_ptr 有诸多缺点,例如,不能调用 delete[],并且,如果 auto_ptr 出现错误,只能在运行时检测而无法在编译时检测。
为此,C++11 标准提出了三个新的智能指针:
这些模板类定义了一个以堆内存空间(new 申请的)指针为参数的构造函数,在创建智能指针对象时,将 new 返回的指针作为参数。同样,这些模板类也定义了析构函数,在析构函数中调用 delete 释放 new 申请的内存。当智能指针对象生命周期结束时,系统调用析构函数释放 new 申请的内存空间。本文将针对这三个智能指针进行讲解。
创建 unique_ptr 智能指针对象的语法格式如下所示:
智能指针对象名称后面的小括号中的参数是一个指针,该指针是 new 运算符申请堆内存空间返回的指针。
unique_ptr 智能指针的用法示例代码如下所示:
当程序运行结束时,即使没有 delete,编译器也会调用 unique_ptr 模板类的析构函数释放 new 申请的堆内存空间。需要注意的是,使用智能指针需要包含
unique_ptr 智能指针对象之间不可以赋值,错误示例代码如下所示:
之所以这样做,是因为 unique_ptr 在实现时是通过所有权的方式管理 new 对象指针的,一个 new 对象指针只能被一个 unique_ptr 智能指针对象管理,即
当发生赋值操作时,智能指针会转让所有权。例如,上述代码中的 pt=ps 语句,如果赋值成功,pt 将拥有对 new 对象指针的所有权,而 ps 则失去所有权,指向无效的数据,成了危险的悬挂指针。如果后面程序中使用到 ps,会造成程序崩溃。
C++98 标准中的 auto_ptr 就是这种实现方式,因此 auto_ptr 使用起来比较危险。C++11 标准为了修复这种缺陷,就将 unique_ptr 限制为不能直接使用
如果需要实现 unique_ptr 智能指针对象之间的赋值,可以调用 C++ 标准库提供的 move() 函数,示例代码如下所示:
每增加一个 shared_ptr 智能指针对象,new 对象指针的引用计数就加 1;当 shared_ptr 智能指针对象失效时,new 对象指针的引用计数就减 1,而其他 shared_ptr 智能指针对象的使用并不会受到影响。只有在引用计数归为 0 时,shared_ptr 才会真正释放所管理的堆内存空间。
shared_ptr 与 unique_ptr 用法相同,创建 shared_ptr 智能指针对象的格式如下所示:
shared_ptr 提供了一些成员函数以更方便地管理堆内存空间,下面介绍几个常用的成员函数。
【示例1】下面通过案例演示 shared_ptr 智能指针的使用,C++ 代码如下:
由运行结果可知,language2 和 language3 仍旧指向地址为 0x1410e70 的堆内存空间,而 language1 指向 00000000,即 nullptr 的值。

图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。
本例可以通过 pt1 修改 new 对象中的数据,但是,由于 weak_ptr 对 new 对象没有所有权,因此无法通过 pw 修改 new 对象中的数据。
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
对内存管理来说,手动释放内存并不是一个好的解决方案,C++98 标准提供了智能指针 auto_ptr 解决了内存的自动释放问题,但是 auto_ptr 有诸多缺点,例如,不能调用 delete[],并且,如果 auto_ptr 出现错误,只能在运行时检测而无法在编译时检测。
为此,C++11 标准提出了三个新的智能指针:
unique_ptr
、shared_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);代码分析:
- 第一行代码创建了一个 unique_ptr 智能指针对象 pi,用于管理一个 int 类型堆内存空间指针;
- 后两行代码创建了一个 unique_ptr 智能指针对象 pA,用于管理一个 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
- 第 7~9 行代码创建了 shared_ptr 智能指针对象 language1、language2 和 language3,这三个智能指针对象都指向同一块堆内存空间;
- 第 11~13 行代码分别通过三个智能指针对象调用 get() 函数,获取它们所管理的堆内存空间;
- 第 15~17 行代码分别通过三个智能指针对象调用 use_count() 函数,输出 new 对象的引用计数;
- 第 18 行代码通过智能指针对象 language1 调用 reset() 函数,取消 language1 对 new 对象的引用;
- 第 20~22 行代码再次通过三个智能指针对象调用 use_count() 函数,计算 new 对象的引用计数;
- 第23~25 行代码再次通过三个智能指针对象调用 get() 函数,获取它们所管理的堆内存空间。
由运行结果可知,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 对象,但 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智能指针失效!
- 第 4~12 行代码定义了 func() 函数,该函数参数为 weak_ptr<string> 引用 pw,在函数内部,通过 pw 调用 lock() 函数,然后判断返回值是否是有效的 shared_ptr 对象,如果是有效的 shared_ptr 对象,就输出 shared_ptr 对象指向的堆内存空间中的数据;如果不是有效的 shared_ptr 对象,就输出相应的提示信息;
- 第 16~17 行代码创建了 shared_ptr<string> 对象 pt1 和 pt2,它们指向同一块堆内存空间;
- 第 19 行代码,创建 weak_ptr<string> 对象 pw,并将 pt1 赋值给 pw。此时,pw、pt1 和 pt2 指向同一块堆内存空间;
- 第 20 行代码调用 func() 函数,并将 pw 作为参数传入;
- 第 21~23 行代码,通过 pt1 将堆内存空间中的数据修改为“Java”,并取消 pt1 对 new 对象的引用,然后调用 func() 函数;
- 第 24~25 行代码,取消 pt2 对 new 对象的引用,并调用 func() 函数。此时,new 对象的 shared_ptr 引用计数为 0,weak_ptr 的 lock() 函数返回值为 nullptr。
本例可以通过 pt1 修改 new 对象中的数据,但是,由于 weak_ptr 对 new 对象没有所有权,因此无法通过 pw 修改 new 对象中的数据。
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。