从C语言到C++_36(智能指针RAII)auto_ptr+unique_ptr+shared_ptr+weak_ptr(下)

简介: 从C语言到C++_36(智能指针RAII)auto_ptr+unique_ptr+shared_ptr+weak_ptr

从C语言到C++_36(智能指针RAII)auto_ptr+unique_ptr+shared_ptr+weak_ptr(中):https://developer.aliyun.com/article/1522496

6. weak_ptr

       weak_ptr是为解决循环引用问题而产生的,可以把weak_ptr当作shared_ptr的小跟班,weak_ptr主要用shared_ptr来构造,所以weak_ptr的拷贝构造以及赋值都不会让引用计数值加1,仅仅是指向资源。

把链表类里的指针换成weak_ptr解决循环引用问题:


6.1 weak_ptr模拟代码

       weak_ptr中只有一个成员变量_ptr,用来指向动态内存空间,在默认构造函数中,仅仅指向动态内存空间。拷贝构造函数和赋值运算符重载函数中,拷贝和赋值的对象都是shared_ptr指针。

       weak_ptr就是用来解决循环引用问题的,所以拷贝和赋值的智能指针必须是shared_ptr。weak_ptr和shared_ptr并不是同一个类,所以获取shared_ptr中的_ptr时,不能直接访问,需要通过shared_ptr的接口get()来获取。

  template<class T> // 辅助型智能指针,配合解决shared_ptr循环引用问题
  class weak_ptr // 没有RAII,不管理资源
  {
  public:
    weak_ptr()
      :_ptr(nullptr)
    {}
    weak_ptr(const shared_ptr<T>& sp)
      :_ptr(sp.get())
    {}
    weak_ptr(const weak_ptr<T>& wp)
      :_ptr(wp._ptr)
    {}
    weak_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
      _ptr = sp.get();
      return *this;
    }
 
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
  protected:
    T* _ptr;
  };

换下命名空间:

效果一样。


7. 定制删除器(了解)

       前面我们自己实现的所有智能指针中,在释放动态内存资源的时候,都只用了delete,也就是所有new出来的资源都是单个的:

       如果是new int[10],或者malloc(20)呢?当需要释放的资源是其他类型的呢?delete肯定就不能满足了,

对于不同类型的资源,需要定制删除器。

先来看库中是如何实现的,这里仅拿shared_ptr为例,unique_ptr也是一样的。

       在构造智能指针的时候,可以传入定制的删除器。可以采用仿函数的方式,lambda的方式以及函数指针的方式,只要是可调用对象都可以。此时的智能指针指向的是动态数组,我们传入的定制删除器也是释放数组的

#include "SmartPtr.hpp"
#include <memory>
 
class A
{
public:
  ~A()
  {
    cout << "~A()" << endl;
  }
  //protected:
  int _a1 = 0;
  int _a2 = 0;
};
 
struct Node
{
  ~Node()
  {
    cout << "~Node" << endl;
  }
 
  int _val;
  rtx::weak_ptr<Node> _next;
  rtx::weak_ptr<Node> _prev;
};
 
template<class T>
struct DeleteArray
{
  void operator()(T* ptr)
  {
    cout << "delete[]" << ptr << endl;
    delete[] ptr;
  }
};
 
template<class T>
struct Free
{
  void operator()(T* ptr)
  {
    cout << "free" << ptr << endl;
    free(ptr);
  }
};
 
int main()
{
  // 仿函数对象
  std::shared_ptr<Node> n1(new Node);
  std::shared_ptr<Node> n2(new Node[5], DeleteArray<Node>());
  std::shared_ptr<int> n3(new int[7], DeleteArray<int>());
  std::shared_ptr<int> n4((int*)malloc(sizeof(12)), Free<int>());
 
  // lambda
  std::shared_ptr<Node> n5(new Node);
  std::shared_ptr<Node> n6(new Node[5], [](Node* ptr) {delete[] ptr; });
  std::shared_ptr<int> n7(new int[7], [](int* ptr) {delete[] ptr; });
  std::shared_ptr<int> n8((int*)malloc(sizeof(12)), [](int* ptr) {free(ptr); });
  //std::shared_ptr<FILE> n9(fopen("test.txt", "w"), [](FILE* ptr) {fclose(ptr); }); //不安全了,但这样也行
 
  std::unique_ptr<Node, DeleteArray<Node>> up(new Node[5]); // unique_ptr只能这样传
 
  return 0;
}


       下面我们类似库里unique_ptr的方法给我们的shared_ptr弄个类似的定制删除器:给我们的shared_ptr配一个默认删除方式的仿函数,执行的是delete ptr。

       在shared_ptr类模板的模板参数中增加一个定制删除器的模板参数,缺省值默认删除方式。在释放资源的时候,在Release()中调用定制的删除器仿函数对象。

  template<class T>
  struct Delete
  {
    void operator()(T* ptr)
    {
      cout << "delete:" << ptr << endl;
      delete ptr;
    }
  };
 
  template<class T, class D = Delete<T>>
  class shared_ptr
  {
  public:
    shared_ptr(T* ptr = nullptr)
      : _ptr(ptr)
      , _pCount(new int(1))
    {}
 
    void Release()
    {
      if (--(*_pCount) == 0) // 防止产生内存泄漏,和析构一样,写成一个函数
      {
        //delete _ptr;
        delete _pCount;
 
        //D del;
        //del(_ptr);
        D()(_ptr); // 对_ptr直接用匿名对象删掉
      }
    }
    ~shared_ptr()
    {
      Release();
    }
...........................略

(下面放了这个程序运行的完整代码)

       标准库中的shared_ptr,在使用定制删除器的时候,是在构造对象时传入函数对象来实现的。我们自己实现的shared_ptr,是在实例化时,传入仿函数类型实现的。

       这是因为,C++11标准库实现的方式和我们不一样,它的更加复杂,专门封装了几个类管理引用计数以及定制删除器等内容。


8. 完整代码

SmartPtr.hpp:

#include <iostream>
using namespace std;
//1、RAII
//2、像指针一样
//3、解决拷贝问题(不同的智能指针的解决方式不一样)
 
namespace rtx
{
  template<class T>
  class auto_ptr
  {
  public:
    auto_ptr(T* ptr)
      :_ptr(ptr)
    {}
    ~auto_ptr()
    {
      cout << "~auto_ptr -> delete: " << _ptr << endl;
      delete _ptr;
    }
    auto_ptr(auto_ptr<T>& ptr)
      :_ptr(ptr._ptr)
    {
      ptr._ptr = nullptr;
    }
    auto_ptr<T>& operator=(auto_ptr<T>& ap)
    {
      if (this != &ap) // 防止自己赋值给自己
      {
        if (_ptr) // 防止释放空,delete空也行
        {
          cout << "operator= -> Delete:" << _ptr << endl;
          delete _ptr;
        }
        _ptr = ap._ptr;
        ap._ptr = nullptr;
      }
      return *this;
    }
 
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
  protected:
    T* _ptr;
  };
 
  template<class T>
  class unique_ptr
  {
  public:
    unique_ptr(T* ptr)
      :_ptr(ptr)
    {}
    ~unique_ptr()
    {
      cout << "~unique_ptr -> delete: " << _ptr << endl;
      delete _ptr;
    }
    unique_ptr(unique_ptr<T>& ptr) = delete;
    unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
 
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
  protected:
    T* _ptr;
  };
 
  template<class T>
  struct Delete
  {
    void operator()(T* ptr)
    {
      cout << "delete:" << ptr << endl;
      delete ptr;
    }
  };
 
  template<class T, class D = Delete<T>>
  class shared_ptr
  {
  public:
    shared_ptr(T* ptr = nullptr)
      : _ptr(ptr)
      , _pCount(new int(1))
    {}
 
    void Release()
    {
      if (--(*_pCount) == 0) // 防止产生内存泄漏,和析构一样,写成一个函数
      {
        //delete _ptr;
        delete _pCount;
 
        //D del;
        //del(_ptr);
        D()(_ptr); // 对_ptr直接用匿名对象删掉
      }
    }
    ~shared_ptr()
    {
      Release();
    }
 
    shared_ptr(const shared_ptr<T>& sp)
      : _ptr(sp._ptr)
      , _pCount(sp._pCount)
    {
      (*_pCount)++;
    }
 
    shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
      //if (this != &sp)
      if (_ptr != sp._ptr) // 防止自己给自己赋值,注意不能比较this,类似s1 = s2; 再来一次s1 = s2;
      {                    // 比较_pCount也行
        //if (--(*_pCount) == 0) // 防止产生内存泄漏,和析构一样,写成一个函数
        //{
        //  delete _ptr;
        //  delete _pCount;
        //}
        Release();
 
        _ptr = sp._ptr;
        _pCount = sp._pCount;
        (*_pCount)++;
      }
      return *this;
    }
 
    int use_count()
    {
      return *_pCount;
    }
    T* get() const
    {
      return _ptr;
    }
 
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
  protected:
    T* _ptr;
    int* _pCount;// 引用计数,有多线程安全问题,学了linux再讲,不能用静态成员
  };
 
  template<class T> // 辅助型智能指针,配合解决shared_ptr循环引用问题
  class weak_ptr // 没有RAII,不管理资源
  {
  public:
    weak_ptr()
      :_ptr(nullptr)
    {}
    weak_ptr(const shared_ptr<T>& sp)
      :_ptr(sp.get())
    {}
    weak_ptr(const weak_ptr<T>& wp)
      :_ptr(wp._ptr)
    {}
    weak_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
      _ptr = sp.get();
      return *this;
    }
 
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
  protected:
    T* _ptr;
  };
}

Test.cpp:

#include "SmartPtr.hpp"
#include <memory>
 
class A
{
public:
  ~A()
  {
    cout << "~A()" << endl;
  }
  //protected:
  int _a1 = 0;
  int _a2 = 0;
};
 
struct Node
{
  ~Node()
  {
    cout << "~Node" << endl;
  }
 
  int _val;
  rtx::weak_ptr<Node> _next;
  rtx::weak_ptr<Node> _prev;
};
 
template<class T>
struct DeleteArray
{
  void operator()(T* ptr)
  {
    cout << "delete[]" << ptr << endl;
    delete[] ptr;
  }
};
 
template<class T>
struct Free
{
  void operator()(T* ptr)
  {
    cout << "free" << ptr << endl;
    free(ptr);
  }
};
 
int main()
{
   仿函数对象
  //std::shared_ptr<Node> n1(new Node);
  //std::shared_ptr<Node> n2(new Node[5], DeleteArray<Node>());
  //std::shared_ptr<int> n3(new int[7], DeleteArray<int>());
  //std::shared_ptr<int> n4((int*)malloc(sizeof(12)), Free<int>());
 
   lambda
  //std::shared_ptr<Node> n5(new Node);
  //std::shared_ptr<Node> n6(new Node[5], [](Node* ptr) {delete[] ptr; });
  //std::shared_ptr<int> n7(new int[7], [](int* ptr) {delete[] ptr; });
  //std::shared_ptr<int> n8((int*)malloc(sizeof(12)), [](int* ptr) {free(ptr); });
  std::shared_ptr<FILE> n9(fopen("test.txt", "w"), [](FILE* ptr) {fclose(ptr); }); //不安全了,但这样也行
 
  //std::unique_ptr<Node, DeleteArray<Node>> up(new Node[5]); // unique_ptr只能类似这样传
 
  rtx::shared_ptr<Node> n1(new Node);
  rtx::shared_ptr<Node, DeleteArray<Node>> n2(new Node[5]);
  rtx::shared_ptr<int, DeleteArray<int>> n3(new int[5]);
  rtx::shared_ptr<int, Free<int>> n4((int*)malloc(sizeof(12)));
 
  return 0;
}



9. 笔试面试题

      面试很大几率会让手撕一个指针指针,如果没有要求的话可以写一个uniqeu_ptr,别写auto_ptr就行,有要求的话就应该就是shared_ptr了,所以智能指针的模拟实现应该闭着眼都能手撕出来。

笔试面试常问问题:

上面的问题博客上面都讲了,总结下智能指针的发展历史:


9.1 智能指针的发展历史

       C++98中的auto_ptr,存在非常大的缺陷,在拷贝构造或者赋值的时候,原本的auto_ptr会被置空,所以这个智能指针存在非常大的缺陷,很多地方都禁止使用。

       C++11中的unique_ptr,禁止了拷贝和赋值,直接避免了auto_ptr可能存在的缺陷,是一个独一无二的智能指针,但是它不能拷贝和赋值。

       C++11又提供了shared_ptr,通过引用计数的方式解决了不能拷贝和赋值的缺陷,并且通过互斥锁保证了shared_ptr本身的线程安全,但是它的死穴是循环引用。

       C++11为了解决shared_ptr的循环引用问题,又提供了weak_ptr智能指针,通过仅指向不管理的方式解决了这个问题。

在使用的时候要根据具体情况选择合适的智能指针,切记最好不要使用auto_ptr。


       C++委员会还发起了一个库,叫boost库,这个库可以理解为C++标准库的先行版,boost库中好用的东西会被C++标准库收录,标准库中的智能指针就是参照boost库中的智能指针再加以修改定义出来的。


C++11和boost中智能指针的关系


① C++ 98 中产生了第一个智能指针auto_ptr。


② C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr。


③ C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。


④ C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost 的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

9.2 笔试选择题:

1. 以下那个误操作不属于资源泄漏()

A.打开的文件忘记关闭


B.malloc申请的空间未通过free释放


C.栈上的对象没有通过delete销毁


D.内存泄漏属于资源泄漏的一种,但资源泄漏不仅仅是内存泄漏


2. 下面那个说法可以表示资源泄漏()


A.从商店买东西


B.借钱不还


C.买房子交首付


D.办信用卡


3. 下面关于内存泄漏的说法正确的是()


A.如果对程序影响不是很大的情况下,泄漏一两个字节不是很重要


B.内存没有释放时,进程在销毁的时候会统一回收,不用担心


C.内存泄漏不一定会对系统马上造成影响,可以不着急进行处理


D.写代码时要有良好的编码规范,万一发生内存泄漏要及时处理


4. 关于RAII下面说法错误的是()


A.RAII的实现方式就是在构造函数中将资源初始化,在析构函数中将资源清理掉


B.RAII方式管理资源,可以有效避免资源泄漏问题


C.所有智能指针都借助RAII的思想管理资源


D.RAII方式管理锁,有些场景下可以有效避免死锁问题


5. 下面关于auto_ptr的说法错误的是()


A.auto_ptr智能指针是在C++98版本中已经存在的


B.auto_ptr的多个对象之间,不能共享资源


C.auto_ptr的实现原理是资源的转移


D.auto_ptr完全可以正常使用


6. 下面关于unique_ptr说法错误的是()


A.unique_ptr是C++11才正式提出的


B.unique_ptr可以管理一段连续空间


C.unique_ptr不能使用其拷贝构造函数


D.unique_ptr的对象之间不能相互赋值



7. 下面关于shared_ptr说法错误的是 ( )


A.shared_ptr是C++11才正式提出来的


B.shared_ptr对象之间可以共享资源


C.shared_ptr可以应用于任何场景


D.shared_ptr是借助引用计数的方式实现的


8. 下面关于weak_ptr的说法错误的是()


A.weak_ptr与shread_ptr的实现方式类似,都是通过引用计数的方式实现的


B.weak_ptr的对象可以独立管理资源


C.weak_ptr的唯一作用就是解决shared_ptr中存在的循环引用问题


D.weak_ptr一般情况下都用不到

9.3 选择题答案及解析

1. C


A:属于,打开的文件用完时一定要关闭


B:属于,堆上申请的空间,需要用户显式的释放


C:不属于,栈上的对象不需要释放,函数结束时编译器会自动释放


D:正确,资源泄漏包含的比较广泛,比如文件未关闭、套接字为关闭等


2. B                


从系统中动态申请的资源,一定要记着及时归还,否则别人可能就使用不了,或者申请失败。就像借钱一样


3. D


A:错误,一两个字节不处理时可能会因小失大,很多个两字节,就是很大的一块内存空间


B:错误,虽然进程退出时会回收,但是进程为退出时可能会影响程序性能甚至会导致崩溃


C:错误,只要发现了就要及时处理,否则,说不定什么时候程序就会崩溃


D:正确,内存泄漏最好不要发生,万一发生了一定要及时处理


4. C


C:错误,weak_ptr不能单独管理资源,必须配合shared_ptr一块使用,解决shared_ptr中存在的 循环引用问题


5. D


A:正确


B:正确,因为auto_ptr采用资源管理权转移的方式实现的,比如:用ap1拷贝构造ap2时,ap1中 的资源会转移给     ap2,而ap1与资源断开联系


C:正确


D:错误,可以使用,但是不建议用,因为有缺陷,标准委员会建议:什么情况下都不要使用auto_ptr


6. B


A:正确


B:错误,C++11中提供的智能指针都只能管理单个对象的资源,没有提供管理一段空间资源的智能指针


C:正确,因为unique_ptr中已经将拷贝构造函数和赋值运算符重载delete了


D:正确,原因同C


7. C


C:错误,有些场景下shared_ptr可能会造成循环引用,必须与weak_ptr配合使用


8. B


A:正确,weak_ptr和shared_ptr都是通过引用计数实现,但是在底层还是有区别的


B:错误,weak_ptr不能单独管理资源,因为其给出的最主要的原因是配合shared_ptr解决其循环引用问题


C:正确,处理解决shared_ptr的循环引用问题外,别无它用


D:正确

本篇完。

       shared_ptr还有线程安全问题没办法讲,需要学完Linux多线程后再讲。C++一些关于Linux的内容更新到Linux多线程的内容后后再放出来,应该一两篇就够了。

目录
相关文章
|
24天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
45 0
|
7天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
25 4
|
23天前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
23天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
15 2
|
23天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
23天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
30天前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
29天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
30天前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
30天前
|
C语言
C语言指针(3)
C语言指针(3)
11 1