深入理解 C++ 智能指针

2024-06-17 1107阅读

文章目录

  • 一、引言
  • 二、 原始指针的问题
    • 1、原始指针的问题
    • 2、智能指针如何解决这些问题
    • 三、智能指针的类型
    • 四、std::shared_ptr
      • 1、shared_ptr使用
      • 2、shared_ptr的使用注意事项
      • 3、定制删除器
      • 4、shared_ptr的优缺点
      • 5、shared_ptr的模拟实现
      • 五、std::unique_ptr
        • 1、unique_ptr的使用
        • 2、unique_ptr的使用注意事项
        • 3、定制删除器
        • 4、unique_ptr的优缺点
        • 5、unique_ptr的模拟实现
        • 六、std::weak_ptr
        • 七、RAII模式:资源获取即初始化
        • 八、再谈删除器

          一、引言

          智能指针是现代 C++ 编程中的重要概念,它们为程序员提供了一种更安全、更方便地管理动态内存的方式。在传统的 C++ 编程中,手动管理内存通常会导致一系列问题,例如内存泄漏、悬空指针以及释放已释放的内存等。智能指针的出现解决了这些问题,使得 C++ 编程更加健壮、安全和高效。

          智能指针本质上是一种对象,它模拟了指针的行为,但具有自动管理内存的功能。通过使用智能指针,可以避免手动调用 new 和 delete 来分配和释放内存,从而减少了出错的可能性。


          二、 原始指针的问题

          1、原始指针的问题

          在使用原始指针(raw pointers)时,程序员需要手动管理内存的生命周期,这包括分配和释放内存。这种管理方式非常直接但也很容易出错,常见的问题有:

          1. 内存泄漏(Memory Leaks):

            • 当动态分配的内存不再需要时,程序员需要显式地释放它。如果忘记释放内存,或者由于某种原因(如异常)导致内存释放的代码没有被执行,那么这块内存就会被永久占用,导致内存泄漏。
            • 内存泄漏在长时间运行的程序中尤为严重,因为它们会逐渐消耗所有可用的内存,最终导致程序崩溃。
            • 悬空指针(Dangling Pointers):

              • 如果一个指针被赋予了动态分配的内存地址,并且随后该内存被释放了,但是指针的值并没有被置为nullptr或重新分配其他地址,那么这个指针就被称为悬空指针。
              • 使用悬空指针会导致不可预测的行为,因为这块内存可能已经被操作系统分配给其他部分使用,或者已经被其他代码覆盖。
              • 双重释放(Double Deletion):

                • 如果同一块内存被释放了两次,这通常会导致运行时错误,因为第二次释放尝试会试图操作一个已经被标记为“已释放”的内存块。

          2、智能指针如何解决这些问题

          智能指针是C++标准库提供的一种自动管理内存的机制,它们通过封装原始指针并提供额外的功能来自动处理内存的生命周期。

          1. 自动内存释放:

            • 智能指针在析构时会自动释放它们所指向的内存,从而避免了内存泄漏的问题。
            • 例如,std::unique_ptr在析构时会调用delete来释放内存,而std::shared_ptr则使用引用计数来确保当最后一个shared_ptr被销毁时,内存才会被释放。
            • 防止悬空指针:

              • 智能指针在释放内存后会将其置为nullptr,从而避免了悬空指针的问题。
              • 这意味着即使尝试访问一个已经被销毁的智能指针,它也会安全地返回,而不会导致未定义的行为。
              • 防止双重释放:

                • 由于智能指针在析构时只释放一次内存,因此它们可以防止双重释放的问题。
                • 当将一个智能指针赋值给另一个智能指针时(例如,通过赋值或移动操作),原始的智能指针会自动放弃对内存的所有权,从而确保同一块内存不会被多次释放。

          总的来说,智能指针通过自动管理内存的生命周期和提供额外的安全性检查来解决了原始指针常见的问题。然而,它们并不是万能的,仍然需要程序员谨慎使用以避免其他类型的错误。


          三、智能指针的类型

          当谈到C++中的智能指针时,通常指的是std::unique_ptr、std::shared_ptr和std::weak_ptr这三种类型。它们在管理动态内存分配和资源所有权方面提供了更安全和方便的方法。这三种类型都定义在memory头文件中。

          1. std::unique_ptr:
            • 特点:std::unique_ptr提供了独占所有权的智能指针。这意味着同一时间只能有一个std::unique_ptr指向同一个资源,当指针超出范围或被销毁时,它所指向的资源会被自动释放。
            • 适用场景:当需要确保资源只有一个所有者时,std::unique_ptr是一个很好的选择。比如,当在函数中分配了一个资源,但是需要在函数返回后释放资源时,使用std::unique_ptr可以确保资源在函数退出时被正确释放。
            • std::shared_ptr:
              • 特点:std::shared_ptr允许多个指针共享同一个资源。它使用引用计数来跟踪资源的所有者数量,并在没有所有者时释放资源。
              • 适用场景:当需要多个指针共享同一资源,并且不清楚哪个指针会最后释放资源时,std::shared_ptr是一个很好的选择。比如,当需要在多个地方引用同一个对象,但不想手动跟踪所有权时,使用std::shared_ptr可以简化管理。
              • std::weak_ptr:
                • 特点:std::weak_ptr是一种弱引用智能指针,它不增加资源的引用计数,指向std::shared_ptr所管理的对象。它用于解决std::shared_ptr可能导致的循环引用问题。
                • 适用场景:当需要引用std::shared_ptr所管理的资源,但不希望增加资源的引用计数时,可以使用std::weak_ptr。比如,在观察者模式中,观察者可能需要引用被观察者,但不应该影响被观察者的生命周期。

          总的来说,选择哪种智能指针类型取决于需求和设计。如果需要确保资源只有一个所有者,使用std::unique_ptr;如果需要多个所有者,使用std::shared_ptr;如果需要避免循环引用,使用std::weak_ptr。


          四、std::shared_ptr

          深入理解 C++ 智能指针

          std::shared_ptr 是 C++11 引入的一个智能指针,用于管理动态分配的对象。它的主要特点是可以共享所有权,并通过引用计数来管理资源的释放,它具有以下特点:

          • 共享所有权:std::shared_ptr 允许多个指针共享对同一资源的所有权。这意味着当最后一个指向资源的 std::shared_ptr 被销毁时,资源才会被释放。

          • 引用计数:std::shared_ptr 内部维护一个引用计数器,用于跟踪有多少个 std::shared_ptr 指向相同的资源。每当创建或销毁一个 std::shared_ptr 时,引用计数都会相应地增加或减少。

            使用场景:

            • 多个所有者:当需要多个对象共享同一资源的所有权时,std::shared_ptr 是一个很好的选择。比如,在设计图形用户界面(GUI)时,多个对象可能需要访问同一块内存或同一个文件资源。
            • 循环引用:std::shared_ptr 可以用于解决循环引用的问题,因为它会自动处理对象之间的引用计数,确保在没有被引用时能够正确释放资源。

              1、shared_ptr使用

              make_shared(args):返回一个shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化此对象。

              shared_ptrp(q):p是shared_ptr q的拷贝,此操作会递增q中的计数,因为 p 和 q 现在都指向了相同的资源。这种操作允许多个智能指针共享同一块内存,同时确保在最后一个指针超出作用域时释放资源。q中的指针必须能转换为T*,即 q 所管理的资源类型能够隐式转换为 T 类型的指针。这通常是因为 q 的类型本身是 shared_ptr,并且 T 类型是 q 中指针的类型或者可以从 q 中指针的类型隐式转换为 T*。p=q:p和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放。

              p.unique:若p.use count()为1,返回true;否则返回false。

              p.use_count():返回与p共享对象的智能指针数量;可能很慢,主要用于调试。

              std::shared_ptr 的 reset 函数用于重新分配被管理的资源,或者将其置为空(不管理任何资源)。reset 函数接受一个可选的参数,用于指定新的资源。如果不提供参数,则该 std::shared_ptr 将置为空。

              以下是 std::shared_ptr 的 reset 函数的一般语法:

              void reset(); // 重置为 nullptr,不管理任何资源
              void reset(T* ptr); // 重置为指定的指针 ptr,开始管理该指针指向的资源
              void reset(nullptr_t); // 重置为 nullptr,不管理任何资源
              

              其中 T* ptr 是指向被管理的资源的原始指针,nullptr_t 是空指针类型。使用 reset 函数可以安全地在不同的 std::shared_ptr 之间转移资源的所有权,或者在不再需要资源时释放它。

              以下是一些示例说明了 reset 函数的用法:

              #include 
              #include 
              int main() {
                  // 创建一个 shared_ptr 来管理动态分配的整数
                  std::shared_ptr ptr(new int(42));
                  // 重新分配资源为一个新的整数
                  ptr.reset(new int(100));
                  // 释放资源,置为空指针
                  ptr.reset();
                  return 0;
              }
              

              在这个示例中,我们首先创建了一个 std::shared_ptr 来管理动态分配的整数。然后,我们使用 reset 函数将该 std::shared_ptr 重新分配为指向一个新的整数。最后,我们再次调用 reset 函数,这次没有传递任何参数,将该 std::shared_ptr 置为空指针。

              2、shared_ptr的使用注意事项

              当使用 new 创建对象时,可以将返回的指针包装在 shared_ptr 中,以确保对象的安全共享和自动内存管理。但是若补初始化一个智能指针,它就会被初始化成一个空指针。

              shared_ptr p1;
              shared_ptr p2(new int(1));
              

              需要注意的是,接受指针参数的智能指针的构造函数是explicit的。因此,我们不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式来初始化智能指针:

              shared_ptr p1 = new int(1024); 	//错误
              shared_ptr p2(new int(1024));	//正确
              

              p1的初始化隐式地要求编译器用一个new返回的int*来创建一个shared_ptr。

              由于我们不能进行内置指针到智能指针间的隐式转换,因此这条初始化语句是错误的。出于相同的原因,一个返回shared_ptr的函数不能在其返回语句中隐式转换一个普通指针:

              shared_ptrclone(int p){
                  return new int(p); 
              }//错误:不存在从 "int *" 转换到 "std::shared_ptr" 的适当构造函数
              

              我们必须将其显式绑定到一个想要返回的指针上:

              shared_ptrclone(int p){
                  return shared_ptr(new int(p));
              }//正确:显式地用int*创建shared ptr    
              

              默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用 delete 释放它所关联的对象。我们可以将智能指针绑定到一个指向其他类型的资源的指针上,但是为了这样做,必须提供自己的操作来替代delete。

              **混合使用智能指针和普通指针可能导致内存管理问题。**智能指针会自动管理其所指向的内存资源,而普通指针则需要手动管理内存。混合使用时,可能会导致重复释放内存或者内存泄漏等问题。

              重复释放内存:

              #include 
              int main() {
                  int* rawPtr = new int(5);
                  {
                      std::shared_ptr smartPtr(rawPtr);
                      // 这里会发生问题,因为当 unique_ptr 离开作用域时,它会尝试释放内存。
                      // 而 rawPtr 本身并不知道 smartPtr 已经释放了内存,因此可能导致重复释放。
                  }
                  delete rawPtr; // 这里会导致重复释放内存,造成未定义行为。
                  return 0;
              }
              

              不能使用get初始化另一个智能指针或为智能指针赋值。这是因为智能指针的设计初衷是为了自动管理资源。使用get方法获得底层指针,并且将其用于初始化另一个智能指针或者直接赋值给另一个智能指针,会导致资源的所有权问题。因为这样做会使得两个智能指针都认为自己拥有资源,从而可能导致重复释放资源或者其他未定义行为。

              在混合使用智能指针和原始指针时,get 函数提供了一种将指针传递给无法接受智能指针的代码的方法。但是,强调了这并不意味着可以安全地将 get 返回的指针传递给另一个智能指针,因为这可能导致内存所有权混乱和未定义行为。举例说明,当使用 get 返回的指针来初始化另一个智能指针时,每个智能指针都认为自己拥有该资源,这可能导致同一块内存被重复释放,或者在使用时发生未定义行为。因此,为了避免这种情况,强调了永远不要将 get 返回的指针用于初始化另一个智能指针或为另一个智能指针赋值。

              #include 
              #include 
              int main() {
                  std::shared_ptr ptr1(new int(42));
                  // 使用 get 返回的原始指针来初始化另一个智能指针
                  std::shared_ptr ptr2(ptr1.get()); // 错误的做法!
                  
                  // 此时,ptr1 和 ptr2 都认为自己拥有该资源,这会导致问题
                  // 当程序结束时,ptr1 和 ptr2 都尝试释放相同的内存,导致未定义行为
                  return 0;
              }
              

              在这个示例中,我们尝试使用 ptr1 的原始指针来初始化 ptr2,这是一种错误的做法。现在两个 std::shared_ptr 都认为它们拥有相同的资源。

              这样做可能导致内存重复释放的问题或者更糟糕的未定义行为。所以,对于 std::shared_ptr,同样要避免使用 get 返回的指针来初始化另一个智能指针或为另一个智能指针赋值。

              3、定制删除器

              为了为 std::shared_ptr 定制删除器,可以在创建 std::shared_ptr 对象时,提供一个自定义的删除器函数对象。这个删除器函数对象将在 std::shared_ptr 的引用计数变为0时被调用,以释放所管理的资源。以下是一个简单的示例,演示了如何为 std::shared_ptr 提供自定义的删除器:

              #include 
              #include 
              // 自定义删除器函数对象
              struct CustomDeleter {
                  void operator()(int* p) const {
                      std::cout 
                  // 使用自定义删除器创建 shared_ptr
                  std::shared_ptr
                  void operator()(int* p) const {
                      std::cout 
                  // 创建 shared_ptr,并传递自定义删除器
                  std::shared_ptr
              public:
                  std::weak_ptr
                      std::cout 
                      std::cout 
              public:
                  std::weak_ptr
                      std::cout 
                      std::cout 
                  std::shared_ptr
              public:
                  MyClass() {
                      std::cout 
                      std::cout 
                  // 创建一个智能指针,共享一个 MyClass 实例的所有权
                  std::shared_ptr
                      // 创建另一个智能指针,共享相同的 MyClass 实例的所有权
                      std::shared_ptr
              public:
                  // RAII
                  shared_ptr(T* ptr = nullptr)
                      :_ptr(ptr)
                          , _pcount(new int(1))
                      {}
                  template}
                  ~shared_ptr() {
                      if (_ptr) {
                          release();
                      }
                  }
                  shared_ptr(const shared_ptr
                      _ptr = sp._ptr;
                      _pcount = sp._pcount;
                      ++(*_pcount);
                  }
                  void release() {
                      if (--(*_pcount) == 0) {
                          cout 
                      //if (&sp != this) //分析 为什么不行  sp1 = sp2;  sp2是sp1构造的。
                      if (sp._ptr != _ptr) {
                          release();
                          _ptr = sp._ptr;
                          _pcount = sp._pcount;
                          ++(*_pcount);
                      }
                      return *this;
                  }
                  int use_count() { return *_pcount; }
                  T& operator*() { return *_ptr; }
                  T* operator-() { return _ptr; }
                  T* get()const { return _ptr; }
              private:
                  T* _ptr;
                  int* _pcount;
                  std::functiondelete ptr;  };
              };
              
              public:
                  MyClass() {
                      std::cout 
                      std::cout 
                  // 使用移动构造
                  std::unique_ptr
                  return std::make_unique
                  std::unique_ptr
                  delete ptr;
              }
              std::unique_ptr
                  delete ptr;
              });
              
                  void operator()(int* ptr) const {
                      delete ptr;
                  }
              };
              std::unique_ptr
                  delete ptr;
              });
               delete ptr; };
              std::unique_ptr
              public:
                  MyClass() { std::cout  std::cout  std::cout 
                  // 创建一个 std::unique_ptr,管理 MyClass 的对象
                  std::unique_ptr
                      std::cout 
              public:
                  // RAII
                  unique_ptr(T* ptr)
                      :_ptr(ptr)
                      {}
                  ~unique_ptr() {
                      reset();
                  }
                  // 删除拷贝构造函数和拷贝赋值运算符,确保只有一个 unique_ptr 可以管理资源
                  unique_ptr(const unique_ptr}
                  // 移动赋值运算符
                  unique_ptr
                      if (this != &other) {
                          reset(other.release());
                      }
                      return *this;
                  }
                  T& operator*() const { return *_ptr; }
                  T* operator-() const { return _ptr; }
                  // 返回指向被管理资源的原始指针
                  T* get() const noexcept { return _ptr; }
                  // 释放资源所有权
                  T* release() noexcept {
                      T* releasedPtr = _ptr;
                      _ptr = nullptr;
                      return releasedPtr;
                  }
                  // 重置 unique_ptr,释放当前资源并接管新资源
                  void reset(T* ptr = nullptr) noexcept {
                      if (_ptr != ptr) {
                          delete _ptr;
                          _ptr = ptr;
                      }
                  }
              private:
                  T* _ptr;
              };
              
              public:
                  void setB(std::shared_ptr
                      _b = b;
                  }
              private:
                  std::weak_ptr
              public:
                  void setA(std::shared_ptr
                      _a = a;
                  }
              private:
                  std::weak_ptr
                  std::shared_ptr
                  std::unique_ptr
                  std::cout 
                  int extraParam = 42;
                  // 使用 lambda 表达式捕获额外参数
                  auto deleter = [&extraParam](int* ptr) {
                      std::cout 
                  // 额外参数
                  int extra_param = 10;
                  // 使用 lambda 表达式作为删除器,并捕获额外参数
                  auto customDeleter = [&extra_param](int* ptr) {
                      std::cout 
                  std::cout 
                  // 额外参数
                  int extra_param = 10;
                  // 使用 std::bind 绑定函数和额外参数,创建删除器
                  auto deleter = std::bind(customDeleter, std::placeholders::_1, extra_param);
                  // 创建 unique_ptr,并指定删除器
                  std::unique_ptr
VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]