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笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
ICP备案:
公安联网备案: