C++ 智能指针

一、内存泄漏


假如你有一个大房子,里面有很多房间。每次你进入一个房间,你都会在里面放一些东西,但你从不清理这些房间。随着时间的推移,房间会变得越来越满,最终你可能找不到地方放新的东西,甚至可能连门都打不开。内存泄漏就是这样一种情况,程序在运行过程中分配了内存,但没有及时释放,导致可用内存越来越少,最终可能导致程序崩溃或系统变慢。

定义:​
内存泄漏是指程序在动态分配内存后,未能正确释放这些内存,导致内存资源的浪费。随着时间的推移,未被释放的内存越来越多,最终可能导致系统资源耗尽。

示例:​

void memoryLeakExample() {
    while (true) {
        int* ptr = new int[1000]; // 分配内存
        // 忘记 delete[] ptr; 导致内存泄漏
    }
}

二、智能指针的作用


假设你有一个自动清洁机器人(智能指针),它会自动清理你家里的垃圾(管理内存)。你不需要手动去清理,机器人会在完成任务后自动关闭(释放内存)。这样,我们就不用担心忘记清理或者清理过度的问题。

定义:​
智能指针是一种用于管理动态分配内存的对象,它们可以自动释放所管理的内存,从而避免内存泄漏和悬挂指针等问题。

三、C++98中的智能指针

定义:​
C++98中没有标准库提供的智能指针,但我们可以使用自定义的RAII类来管理资源。例如,std::auto_ptr是C++98中引入的一个简单的智能指针,但它有一些严重的缺陷,最终在C++11中被弃用。

示例:​

#include 
#include  // 包含auto_ptr的头文件

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructed" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructed" << std::endl; }
};

int main() {
    std::auto_ptr ptr(new MyClass()); // 创建一个auto_ptr
    // 注意:auto_ptr的所有权会转移,容易导致问题
    return 0;
}
// 结论:auto_ptr是一个失败设计,例如以下情况
int main()
{
 std::auto_ptr sp1(new int);
 std::auto_ptr sp2(sp1); // 管理权转移

 // sp1悬空
 *sp2 = 10;
 cout << *sp2 << endl;
 cout << *sp1 << endl;
 return 0;
}

四、C++11中的智能指针

定义:​
C++11标准库提供了三种智能指针:

  • std::unique_ptr:独占所有权,确保只有一个指针可以指向某个对象。
  • std::shared_ptr:共享所有权,使用引用计数管理对象的生命周期。
  • std::weak_ptr:弱引用,不参与引用计数,用于解决循环引用问题。
1. std::unique_ptr

std::unique_ptr是一种独占所有权的智能指针,它确保只有一个指针可以指向某个对象。当std::unique_ptr被销毁时,它所管理的对象也会被自动删除。

代码示例:​

#include 
#include 

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructed" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructed" << std::endl; }
};

int main() {
    std::unique_ptr ptr(new MyClass()); // 创建一个unique_ptr
    // 当ptr离开作用域时,MyClass对象会被自动销毁
    return 0;
}
2. std::shared_ptr

std::shared_ptr是一种共享所有权的智能指针,它可以有多个指针指向同一个对象。std::shared_ptr使用引用计数来管理对象的生命周期,当最后一个std::shared_ptr被销毁时,对象才会被删除。

代码示例:​

#include 
#include 

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructed" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructed" << std::endl; }
};

int main() {
    std::shared_ptr ptr1(new MyClass()); // 创建一个shared_ptr
    {
        std::shared_ptr ptr2 = ptr1; // 共享所有权
        std::cout << "Reference count: " << ptr1.use_count() << std::endl; // 输出2
    } // ptr2离开作用域,引用计数减少
    std::cout << "Reference count: " << ptr1.use_count() << std::endl; // 输出1
    return 0;
} // ptr1离开作用域,引用计数为零,对象被销毁
3. std::weak_ptr

std::weak_ptr是一种弱引用的智能指针,它不参与引用计数,因此不会影响对象的生命周期。std::weak_ptr通常与std::shared_ptr一起使用,用于解决循环引用的问题。

代码示例:​

#include 
#include 

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructed" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructed" << std::endl; }
};

int main() {
    std::shared_ptr sharedPtr(new MyClass()); // 创建一个shared_ptr
    std::weak_ptr weakPtr = sharedPtr; // 创建一个weak_ptr

    if (auto lockedPtr = weakPtr.lock()) { // 尝试获取shared_ptr
        std::cout << "Object is still alive" << std::endl;
    } else {
        std::cout << "Object has been destroyed" << std::endl;
    }

    sharedPtr.reset(); // 销毁sharedPtr

    if (auto lockedPtr = weakPtr.lock()) { // 再次尝试获取shared_ptr
        std::cout << "Object is still alive" << std::endl;
    } else {
        std::cout << "Object has been destroyed" << std::endl;
    }

    return 0;
}
4.智能指针的优势
  1. 自动内存管理:智能指针可以自动释放内存,避免内存泄漏。
  2. 避免悬挂指针:智能指针确保对象在其生命周期结束后被销毁,避免悬挂指针问题。
  3. 简化代码:使用智能指针可以减少手动管理内存的代码,使代码更加简洁和安全。

五、智能指针的原理


智能指针的原理可以类比为一个自动管理资源的系统。每当创建一个智能指针时,系统会记录这个指针所管理的资源。当智能指针离开作用域时,系统会自动释放这个资源。
智能指针的原理基于RAII(Resource Acquisition Is Initialization)技术。RAII是一种编程技术,通过在对象构造时获取资源,在对象析构时释放资源,从而确保资源的正确管理。

详细原理:​

  1. std::unique_ptr:通过模板类实现,内部持有一个指向动态分配内存的指针。当std::unique_ptr对象被销毁时,其析构函数会自动调用delete释放内存。
  2. std::shared_ptr:通过引用计数实现,内部维护一个控制块,记录当前有多少个std::shared_ptr指向同一个对象。当最后一个std::shared_ptr被销毁时,控制块会自动调用delete释放内存。
  3. std::weak_ptr:不参与引用计数,内部持有一个指向std::shared_ptr控制块的指针。通过lock()方法可以尝试获取一个std::shared_ptr,如果对象已经被释放,则返回一个空的std::shared_ptr

六、智能指针的一些问题以及注意事项

常见问题及注意事项:​

  1. 循环引用

    • 问题std::shared_ptr之间如果形成循环引用,会导致内存泄漏,因为引用计数永远不会降为零。
      #include 
      #include 
      
      class Child;
      
      class Parent {
      public:
          std::shared_ptr child;  // Parent 持有 Child 的 shared_ptr
          ~Parent() { std::cout << "Parent 被销毁\n"; }
      };
      
      class Child {
      public:
          std::shared_ptr parent;  // Child 也持有 Parent 的 shared_ptr
          ~Child() { std::cout << "Child 被销毁\n"; }
      };
      
      int main() {
          // 创建 Parent 和 Child 对象
          auto parent = std::make_shared();
          auto child = std::make_shared();
      
          // 互相引用:Parent 指向 Child,Child 指向 Parent
          parent->child = child;
          child->parent = parent;
      
          // 此时 parent 和 child 的引用计数均为 2
          // 当 main 函数结束时,引用计数减为 1,对象无法销毁
          return 0;
      }
    • 解决方法:使用std::weak_ptr来打破循环引用。
      #include 
      #include 
      
      class Child;
      
      class Parent {
      public:
          std::shared_ptr child;
          ~Parent() { std::cout << "Parent 被销毁\n"; }
      };
      
      class Child {
      public:
          std::weak_ptr parent;  // 使用 weak_ptr 避免循环引用
          ~Child() { std::cout << "Child 被销毁\n"; }
      };
      
      int main() {
          auto parent = std::make_shared();
          auto child = std::make_shared();
      
          parent->child = child;
          child->parent = parent;  // weak_ptr 可以直接指向 shared_ptr
      
          // 此时:
          // parent 的引用计数为 1(child->parent 是 weak_ptr,不增加计数)
          // child 的引用计数为 2(parent->child 是 shared_ptr)
      
          // 当 main 函数结束时:
          // parent 的引用计数减为 0,Parent 对象被销毁
          // Parent 销毁后,parent->child 被释放,child 的引用计数减为 1 → 0,Child 对象也被销毁
          return 0;
      }
  2. 所有权问题

    • 问题std::unique_ptr不能被复制,只能被移动。如果错误地尝试复制std::unique_ptr,会导致编译错误。
      #include 
      
      class Box {
      public:
          int volume = 10;
      };
      
      void takeOwnershipBad(std::unique_ptr box) {
          // 使用 box...
      }
      
      int main() {
          auto box1 = std::make_unique();  // 创建一个 unique_ptr
      
          // 错误!尝试复制 unique_ptr(编译失败)
          takeOwnershipBad(box1);  // 拷贝构造函数被删除
      
          return 0;
      }
    • 解决方法:使用std::move来转移所有权。
      #include 
      #include 
      
      class Box {
      public:
          int volume = 10;
          ~Box() { std::cout << "Box 被销毁\n"; }
      };
      
      void takeOwnershipGood(std::unique_ptr box) {
          std::cout << "接收到的 Box 体积: " << box->volume << std::endl;
      }  // 函数结束时,box 被销毁,内存释放
      
      int main() {
          auto box1 = std::make_unique();  // box1 拥有 Box 对象
      
          // 正确:用 std::move 转移所有权给函数参数
          takeOwnershipGood(std::move(box1));  // 正确
      
          // 此时 box1 已不再拥有对象(变为 nullptr)
          if (!box1) {
              std::cout << "box1 已转移所有权,变为空指针\n";
          }
      
          return 0;
      }
  3. 异常安全

    • 问题:如果在智能指针的构造函数中抛出异常,可能会导致资源泄漏。
    • 解决方法:确保智能指针的构造函数是异常安全的,或者使用工厂函数来创建智能指针。
  4. 自定义删除器

    • 问题:默认情况下,智能指针使用delete来释放内存。如果管理的是非堆内存(如文件句柄、网络连接等),需要自定义删除器。
    • 解决方法:在创建智能指针时提供自定义删除器。

你可能感兴趣的:(C++,c++,开发语言,智能指针,C++11)