【C++】智能指针用法详解(非常实用)

简介: 【C++】智能指针用法详解(非常实用)

一、指针与智能指针的区别


1、指针


 C 语言规定所有变量在使用前必须先定义,指定其类型,并按此分配内存单元。指针变量不同于整型变量和其他类型的变量,它是专门用来存放地址的,所以必须将它定义为“指针类型”。


2、智能指针


如果在程序中使用 new 从堆(自由存储区)分配内存,等到不需要时,应使用 delete 将其释放。 C++ 引用了智能指针 auto_ptr,以帮助自动完成这个过程。随后的编程体验(尤其是使用STL)表明,需要有更精致的机制。基于程序员的编程体验和 BOOST 库提供的解决方案,C++11摒弃了 auto_ptr,并新增了三种智能指针:unique_ptr、shared_ptr 和 weak_ptr。所有新增的智能指针都能与 STL 容器和移动语义协同工作。


3、智能指针和普通指针的区别


 智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,区别是它负责自动释放所指的对象,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。


二、智能指针


1、智能指针的种类


 C++中的智能指针有4种,分别为:shared_ptrunique_ptrweak_ptrauto_ptr,其中 auto_ptr被C++11弃用。


2、使用智能指针的原因


申请的空间(即new出来的空间),在使用结束时,需要 delete 掉,否则会形成内存碎片。在程序运行期间,new 出来的对象,在析构函数中 delete 掉,但是这种方法不能解决所有问题,因为有时候 new 发生在某个全局函数里面,该方法会给程序员造成精神负担。此时,智能指针就派上了用场。使用智能指针可以很大程度上避免这个问题,因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,析构函数会自动释放资源。所以,智能指针的作用原理就是在函数结束时自动释放内存空间,避免了手动释放内存空间。


3、智能指针的使用


(1)autoi_ptr(C++98 的方案,C++11已经弃用)


 auto_ptr 智能指针采用所有权模式;

auto_ptr<string> p1(new string("I reigned loney as a cloud.")); 
auto_ptr<string> p2; 
p2=p1; //auto_ptr不会报错

说明:此时不会报错,p2 剥夺了 p1 的所有权(相当于是两个指针同时指向一块内存),但是当程序运行时访问 p1 将会报错。所以auto_ptr 的缺点是:存在潜在的内存崩溃问题。


(2)unique_ptr(替换 auto_ptr)


      unique_ptr 实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对 象。它对于避免资源泄露,例如,以 new 创建对象后因为发生异常而忘记调用 delete 时的情形特别 有用。


采用所有权模式,和上面例子一样。

unique_ptr<string> p3(new string("I reigned loney as a cloud.")); 
unique_ptr<string> p4; 
p4=p3; //此时会报错


 编译器认为P4=P3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。 另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做(即源 unique_ptr 是临时右值时,源 unique_ptr 在赋值后不会出现被访问,所以编译器不会报错),比如:

unique_ptr<string> pu1(new string ("hello world")); 
unique_ptr<string> pu2; 
pu2 = pu1;                                    // #1 not allowed 
unique_ptr<string> pu3; 
pu3 = unique_ptr<string>(new string ("You")); // #2 allowed

说明:其中#1留下悬挂的 unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的 unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销 毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。


注意:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++ 有一个标准库函数 std::move(),让你能够将一个 unique_ptr 赋给另一个。例如:

unique_ptr<string> ps1, ps2; 
ps1 = demo("hello"); 
ps2 = move(ps1); 
ps1 = demo("alexia"); 
cout << *ps2 << *ps1 << endl;


(3)shared_ptr


 shared_ptr 实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计 数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除 了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用 release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。


      shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。


shared_ptr 智能指针相关成员函数:


1)use_count:返回引用计数的个数;


2)unique:返回是否是独占所有权( use_count 为 1);


3)swap:交换两个 shared_ptr 对象(即交换所拥有的对象);


4)reset:放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少;


5)get:返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr sp(new int(1)); sp 与 sp.get() 是等价的;


(4)weak_ptr


weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象。进行该对象的内存管理的是那个强引用的 shared_ptr。weak_ptr 只是提供了对管理对象的一个访问手 段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增 加或减少。weak_ptr 是用来解决 shared_ptr 相互引用时的死锁问题,如果说两个 shared_ptr 相互 引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引 用,不会增加对象的引用计数,和 shared_ptr 之间可以相互转化,shared_ptr 可以直接赋值给 它,它可以通过调用 lock 函数来获得 shared_ptr

class B; 
class A {
public:
    shared_ptr<B> pb_; 
    ~A() { 
        cout<<"A delete\n"; }
};
class B {
public: 
    shared_ptr<A> pa_; 
    ~B() { 
        cout<<"B delete\n"; }
};
void fun() { 
    shared_ptr<B> pb(new B()); 
    shared_ptr<A> pa(new A()); 
    pb->pa_ = pa; 
    pa->pb_ = pb; 
    cout<<pb.use_count()<<endl; 
    cout<<pa.use_count()<<endl; 
}
int main() { 
    fun(); 
    return 0; 
}
1)shared_ptr<B> pb(new B());函数退出时,B被析构,引用计数减一,但是 B 里面还引用了个 A ,此时计数为一,无 法释放资源,如果把两个 share_ptr 中一 个换成 weak_ptr 资源的引用计数为一, 析构后资源会被释放;
2)shared_ptr<A> pa(new A());
3)虚析构、纯虚析构解决解决的是父类指 针释放子类对象问题。

说明:  可以看到 fun 函数中 pa ,pb 之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针 pa,pb 析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有 被释放(AB的析构函数没有被调用),如果把其中一个改为 weak_ptr 就可以了,我们把类A里面 的 shared_ptr pb_; 改为 weak_ptr pb; 运行结果如下,这样的话,资源B的引用开始就只有1,当  pb 析构时,B 的计数变为 0,B 得到释放,B 释放的同时也会使A的计数减一,同时 pa析构时使A的 计数减一,那么 A 的计数为 0,A 得到释放。


注意:我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能 这样访问,pa->pb->print(); 英文pb是一个weak_ptr,应该先把它转化为shared_ptr,如: shared_ptr p = pa->pb_.lock(); p->print();

相关文章
|
20天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
46 4
|
2月前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
2月前
|
存储 C++
c++的指针完整教程
本文提供了一个全面的C++指针教程,包括指针的声明与初始化、访问指针指向的值、指针运算、指针与函数的关系、动态内存分配,以及不同类型指针(如一级指针、二级指针、整型指针、字符指针、数组指针、函数指针、成员指针、void指针)的介绍,还提到了不同位数机器上指针大小的差异。
47 1
|
2月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
33 2
|
2月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
2月前
|
存储 C++ 索引
C++函数指针详解
【10月更文挑战第3天】本文介绍了C++中的函数指针概念、定义与应用。函数指针是一种指向函数的特殊指针,其类型取决于函数的返回值与参数类型。定义函数指针需指定返回类型和参数列表,如 `int (*funcPtr)(int, int);`。通过赋值函数名给指针,即可调用该函数,支持两种调用格式:`(*funcPtr)(参数)` 和 `funcPtr(参数)`。函数指针还可作为参数传递给其他函数,增强程序灵活性。此外,也可创建函数指针数组,存储多个函数指针。
|
2月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
2月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
29 0
|
3月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
4月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)