目录
  • shared_ptr相互嵌套导致循环引用
    • 代码示例
    • 解释说明
    • 解决方法
  • shared_ptr的层次使用没有导致循环引用
    • 回调函数中的循环引用问题
      • 值捕获
      • 引用捕获
      • 如何解决 
      • 背景问题
      • std::enable_shared_from_this 的作用
      • 使用示例
      • 注意
      • 为什么 std::enable_shared_from_this 是必要的?
    • 总结

      shared_ptr相互嵌套导致循环引用

      代码示例

      #include <iostream>
      #include <memory>
      using namespace std;
      class B;
      class A {
      public:
          std::shared_ptr<B> b_ptr;
          ~A() { std::cout << "A destroyed\n"; }
      };
      class B {
      public:
          std::shared_ptr<A> a_ptr;
          ~B() { std::cout << "B destroyed\n"; }
      };
      int main() {
          // 创建 shared_ptr 对象
          auto a = std::make_shared<A>();
          auto b = std::make_shared<B>();
          // 相互引用
          a->b_ptr = b;
          b->a_ptr = a;
          cout<<"use_count of a:"<<a.use_count()<<endl;
          cout<<"use_count of b:"<<b.use_count()<<endl;
          return 0;
      }

      C++ 智能指针使用不当导致内存泄漏问题解析

      解释说明

      • 创建了两个 std::shared_ptr 对象 a 和 b
      • a 持有 b 的 shared_ptrb 持有 a 的 shared_ptr
      • 当 main 函数结束时,a 和 b 的引用计数不会减少到零,因此它们的析构函数不会被调用。
      • 导致内存泄漏,因为对象 A 和 B 的内存不会被释放。  

      解决方法

      为了避免这种循环引用的问题,可以使用 std::weak_ptrstd::weak_ptr 是一种弱智能指针,它不会增加对象的引用计数。它可以用来打破循环引用,从而防止内存泄漏。

      #include <iostream>
      #include <memory>
      using namespace std;
      class B;  // 先声明类 B,使得 A 和 B 可以互相引用。
      class A {
      public:
          std::shared_ptr<B> b_ptr; // A 拥有 B 的强引用
          ~A() { std::cout << "A destroyed\n"; }
      };
      class B {
      public:
          std::weak_ptr<A> a_ptr; // B 拥有 A 的弱引用
          ~B() { std::cout << "B destroyed\n"; }
          void safeAccess() {
              // 尝试锁定 a_ptr 获取 shared_ptr
              if (auto a_shared = a_ptr.lock()) {
                  // 安全访问 a_shared 对象
                  std::cout << "Accessing A from B\n";
              } else {
                  std::cout << "A is already destroyed, cannot access A from B\n";
              }
          }
      };
      int main() {
          // 创建 shared_ptr 对象
          auto a = std::make_shared<A>();
          auto b = std::make_shared<B>();
          // 互相引用
          a->b_ptr = b;
          b->a_ptr = a;
          // 安全访问
          b->safeAccess();
          cout<<"use_count of a:"<<a.use_count()<<endl;
          cout<<"use_count of b:"<<b.use_count()<<endl;
          return 0; // 在这里,a 和 b 的引用计数将会正确地减少到零,并且它们将会被销毁。
      }

      C++ 智能指针使用不当导致内存泄漏问题解析

      shared_ptr的层次使用没有导致循环引用

      shared_ptr<vector<shared_ptr<pair<string, shared_ptr<string>>>>> jsFiles;

      这个声明表示 jsFiles 是一个 std::shared_ptr,它指向一个 std::vector,向量中的每个元素是一个 std::shared_ptr,指向一个 std::pair 对象,而这个 std::pair 对象中包含一个 std::string 和一个 std::shared_ptr<std::string>。它们之间只是层次结构,没有跨层次的相互引用 。也就是说没有内存泄漏的问题。证明如下:

      #include <iostream>
      #include <vector>
      #include <memory>
      #include <string>
      using namespace std;
      // 自定义 String 类,模拟 std::string
      class MyString {
      public:
          std::string data;
          MyString(const std::string& str) : data(str) {
              std::cout << "MyString created: " << data << std::endl;
          }
          ~MyString() {
              std::cout << "MyString destroyed: " << data << std::endl;
          }
          // 添加输出操作符重载
          friend std::ostream& operator<<(std::ostream& os, const MyString& myStr) {
              os << myStr.data;
              return os;
          }
      };
      // 自定义 Pair 类,模拟 std::pair
      template<typename K, typename V>
      class MyPair {
      public:
          K first;
          V second;
          MyPair(const K& key, const V& value) : first(key), second(value) {
              std::cout << "MyPair created: {" << first << ", " << *second << "}" << std::endl;
          }
          ~MyPair() {
              std::cout << "MyPair destroyed: {" << first << ", " << *second << "}" << std::endl;
          }
      };
      int main() {
          // 创建 jsFiles,它是一个 shared_ptr,指向 vector
          auto jsFiles = std::make_shared<std::vector<std::shared_ptr<MyPair<std::string, std::shared_ptr<MyString>>>>>();
          // 添加元素
          auto innerPair1 = std::make_shared<MyPair<std::string, std::shared_ptr<MyString>>>("file1", std::make_shared<MyString>("content of file1"));
          auto innerPair2 = std::make_shared<MyPair<std::string, std::shared_ptr<MyString>>>("file2", std::make_shared<MyString>("content of file2"));
          jsFiles->push_back(innerPair1);
          jsFiles->push_back(innerPair2);
          // 访问元素
          for (const auto& pairPtr : *jsFiles) {
              std::cout << "Filename: " << pairPtr->first << ", Content: " << *pairPtr->second << std::endl;
          }
          // 离开作用域时,智能指针会自动销毁它们管理的对象
          return 0;
      }

      C++ 智能指针使用不当导致内存泄漏问题解析

      同时也证明了一个结论,构造函数和析构函数的调用顺序是相反的。 

      回调函数中的循环引用问题

      值捕获

      #include <iostream>
      #include <memory>
      #include <functional>
      class MyClass {
      public:
          MyClass() { std::cout << "MyClass created" << std::endl; }
          ~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
          void setCallback(std::function<void()> cb) {
              callback_ = cb;
          }
          void executeCallback() {
              if (callback_) {
                  callback_();
              }
          }
      private:
          std::function<void()> callback_;
      };
      void createNoLeak() {
          auto myObject = std::make_shared<MyClass>();
          myObject->setCallback([=]() {
              std::cout << "Callback executed, myObject use count: " << myObject.use_count() << std::endl;
          });
          myObject->executeCallback();
      }
      int main() {
          createNoLeak();
          std::cout << "End of program" << std::endl;
          return 0;
      }

      C++ 智能指针使用不当导致内存泄漏问题解析

      可以看出myObject最后没有调用析构函数,是shared_ptr循环引用了。

      引用捕获

      如果换为引用捕获,则不会造成 shared_ptr循环引用。虽然这种方式不会增加引用计数,但需要特别注意捕获对象的生命周期,防止在 lambda 被调用时,对象已经被销毁,从而导致未定义行为。

      C++ 智能指针使用不当导致内存泄漏问题解析

      如何解决 

      #include <iostream>
      #include <memory>
      #include <functional>
      class MyClass : public std::enable_shared_from_this<MyClass> {
      public:
          MyClass() { std::cout << "MyClass created" << std::endl; }
          ~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
          void setCallback(std::function<void()> cb) {
              callback_ = cb;
          }
          void executeCallback() {
              if (callback_) {
                  callback_();
              }
          }
      private:
          std::function<void()> callback_;
      };
      void createNoLeak() {
          auto myObject = std::make_shared<MyClass>();
          std::weak_ptr<MyClass> weakPtr = myObject;
          myObject->setCallback([weakPtr]() {
              if (auto sharedPtr = weakPtr.lock()) {
                  std::cout << "Callback executed, object is valid" << std::endl;
              } else {
                  std::cout << "Object already destroyed" << std::endl;
              }
          });
          myObject->executeCallback();
          // 这里 myObject 是按 weak_ptr 捕获,当 createNoLeak() 结束时,myObject 的生命周期也就结束了,并且引用计数=0
      }
      int main() {
          createNoLeak();
          std::cout << "End of program" << std::endl;
          return 0;
      }

      C++ 智能指针使用不当导致内存泄漏问题解析

      • weakPtr.lock() 的使用:持有 std::weak_ptr,并且需要检查或者使用其管理的对象。如果对象仍然存在(即它的 shared_ptr 引用计数大于零),我们希望获取一个 shared_ptr 来安全地使用该对象。否则,weak_ptr.lock() 返回一个空的 shared_ptr
      • std::enable_shared_from_this 是一个非常有用的标准库模板类,用于解决一个特定的问题: 当一个类的成员函数需要创建一个指向自己(this)的 std::shared_ptr 时,这类问题如何安全地实现。 std::enable_shared_from_this

      背景问题

      在使用 std::shared_ptr 管理对象时,有时会遇到需要在类的成员函数中获取该对象的 shared_ptr 的情况。例如,在一个类的成员函数中,如果想要得到一个指向该对象的 shared_ptr,不能简单地使用 std::shared_ptr<MyClass>(this),因为这会创建一个新的 shared_ptr,而不是增加现有的 shared_ptr 的引用计数。这可能导致对象被提前销毁或者多次销毁。

      std::enable_shared_from_this 的作用

      通过继承 std::enable_shared_from_this,类就能够安全地使用 shared_from_this 方法,从而获取一个 shared_ptr,该 shared_ptr 与其他 shared_ptr 共享所有权,而不会重复增加引用计数。

      使用示例

      #include <iostream>
      #include <memory>
      // 定义 MyClass 继承 std::enable_shared_from_this<MyClass>
      class MyClass : public std::enable_shared_from_this<MyClass> {
      public:
          MyClass() { std::cout << "MyClass created" << std::endl; }
          ~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
          // 一个成员函数,它需要返回一个指向自身的 shared_ptr
          std::shared_ptr<MyClass> getSharedPtr() {
              // 使用 shared_from_this 返回一个 shared_ptr
              return shared_from_this();
          }
          void doSomething() {
              auto ptr = shared_from_this(); // 获取 shared_ptr
              std::cout << "Doing something with MyClass instance, ref count: " << ptr.use_count() << std::endl;
          }
      };
      void exampleFunction() {
          // 创建 MyClass 对象的 shared_ptr
          auto myObject = std::make_shared<MyClass>();
          // 调用成员函数获取 shared_ptr
          auto mySharedPtr = myObject->getSharedPtr();
          std::cout << "Reference count after getSharedPtr: " << mySharedPtr.use_count() << std::endl;
          myObject->doSomething();
      }
      int main() {
          exampleFunction();
          return 0;
      }

      注意

      1.创建对象:
      只有通过 std::shared_ptr 创建或管理的对象,才能安全地使用 shared_from_this

      2. 保护避免使用 new 操作符:
      直接使用 new 操作符创建的对象不能正确使用 shared_from_this,这样做可能会导致未定义行为(例如崩溃)。

      为什么 std::enable_shared_from_this 是必要的?

      std::enable_shared_from_this 内部维护了一个弱引用(std::weak_ptr)指向当前对象。这个弱引用确保不会增加引用计数,同时允许 shared_from_this 方法安全地获取 std::shared_ptr,从而真正共享管理的对象,避免不安全的重复引用计数增加。

      通过这样做,C++ STL 提供了一种方便而安全的方式来管理对象的生命周期,特别是在需要从对象内部生成 shared_ptr的情境下。

      总结

      通过继承 std::enable_shared_from_thisMyClass 能够安全地在其成员函数中创建返回指向自身的 std::shared_ptr,避免不必要的重复引用计数,从而有效地管理和共享对象生命周期。这样既提升了代码的安全性,也使得对象生命周期管理变得更加简洁和直观。

      声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。