【问题标题】:About "circular reference", I used weak_ptr but memory leak still happened关于“循环引用”,我使用了weak_ptr,但仍然发生内存泄漏
【发布时间】:2019-09-09 10:10:12
【问题描述】:

我读到了:

How to avoid memory leak with shared_ptr?

我知道我需要使用 weak_ptr 来避免 循环引用

所以我创建了一个小程序来播放循环引用

以下对象(spyder)将被调用

class spyder {
 public:
  spyder(std::string _name): 
    m_name(_name), finger(nullptr)
  {  }
  inline const std::string ask_name() const{
    return m_name;
  }
  std::shared_ptr<spyder> finger;
 private:
  std::string m_name;
};

我在主代码中使用 shared_ptr 和 weak_ptr 调用 spyder

int main(){
  auto sA = std::make_shared<spyder>("A");
  auto sB = std::make_shared<spyder>("B");
  std::weak_ptr<spyder> wp_sA(sA);
  std::weak_ptr<spyder> wp_sB(sB);  
  sA->finger = wp_sB.lock();
  sB->finger = wp_sA.lock();
}

以上代码发生内存泄漏(使用valgrind检查)。

==20753== Memcheck, a memory error detector
==20753== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==20753== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==20753== Command: ./t
==20753== 
==20753== 
==20753== HEAP SUMMARY:
==20753==     in use at exit: 128 bytes in 2 blocks
==20753==   total heap usage: 3 allocs, 1 frees, 72,832 bytes allocated
==20753== 
==20753== LEAK SUMMARY:
==20753==    definitely lost: 64 bytes in 1 blocks
==20753==    indirectly lost: 64 bytes in 1 blocks
==20753==      possibly lost: 0 bytes in 0 blocks
==20753==    still reachable: 0 bytes in 0 blocks
==20753==         suppressed: 0 bytes in 0 blocks
==20753== Rerun with --leak-check=full to see details of leaked memory
==20753== 
==20753== For counts of detected and suppressed errors, rerun with: -v
==20753== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

但是在我将上面的代码修改为:

int main(){
  spyder sA("A"), sB("B");
  std::weak_ptr<spyder> wp_sA( std::make_shared<spyder>("A") ) ;
  std::weak_ptr<spyder> wp_sB( std::make_shared<spyder>("B") );
  sA.finger = wp_sB.lock();
  sB.finger = wp_sA.lock();
}

内存泄漏还没有发生,

==20695== Memcheck, a memory error detector
==20695== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==20695== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==20695== Command: ./t
==20695== 
==20695== 
==20695== HEAP SUMMARY:
==20695==     in use at exit: 0 bytes in 0 blocks
==20695==   total heap usage: 3 allocs, 3 frees, 72,832 bytes allocated
==20695== 
==20695== All heap blocks were freed -- no leaks are possible
==20695== 
==20695== For counts of detected and suppressed errors, rerun with: -v
==20695== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

我对此感到困惑。

【问题讨论】:

  • lock 调用之后会发生什么?你曾经解锁过弱指针吗?请尝试创建一个minimal reproducible example 向我们展示,并从该示例中复制粘贴 Valgrind 的完整输出。
  • 另外我认为你误解了一些东西,在第一个例子中你真正在做的是等于sA-&gt;finger = sB; sB-&gt;finger = sA;。您需要将finger 成员设为std::weak_ptr
  • 嗨@Someprogrammerdude:我已经更新了 Valgrind 的输出。命令是valgrind --leak-check=summary ./t.out
  • 您无法修复具有弱引用的循环参考设计问题。

标签: c++ shared-ptr smart-pointers circular-reference weak-ptr


【解决方案1】:

你根本没有解决循环依赖。

在您的第一个版本中,

sAshared_ptr 管理,并将shared_ptr 存储到sBsB 又由shared_ptr 管理,并将shared_ptr 存储到sA。这意味着它们的引用计数永远不会为 0。

相反,finger 应该是 std::weak_ptr 类型,并且您应该在需要使用它之前只使用 lock()

class spyder {
 public:
  spyder(std::string _name): 
    m_name(_name), finger()
  {  }
  inline const std::string ask_name() const{
    return m_name;
  }
  std::weak_ptr<spyder> finger;
 private:
  std::string m_name;
};

int main(){
  auto sA = std::make_shared<spyder>("A");
  auto sB = std::make_shared<spyder>("B");
  sA->finger = sB;
  sB->finger = sA;
}

你的第二个版本

从一个临时的shared_ptr 构造weak_ptr,这意味着在构造之后你的weak_ptrexpired()。在这个版本中sA.fingersB.finger 都存储nullptr(但没有内存泄漏)。

【讨论】:

  • 嗨@Yksisarvinen:谢谢你的回复。在我修改 std::weak_ptr finger; 后这段代码无法编译,见 std::weak_ptr finger;见 cpp.sh/7a2fm
  • @curlywei 我的错,在发布之前没有运行代码。 std::weak_ptr(以及std::shared_ptr)的默认构造函数将使用nullptr 对其进行初始化。你甚至可以从初始化列表中省略它。现在编辑答案
  • 删除nullptr后,这段代码就可以正常工作了。非常感谢!
【解决方案2】:

您的代码泄漏,因为它仍然具有循环引用。 sA -&gt; sA::finger -&gt; sB -&gt; sB::finger -&gt; sA std::weak_ptr 的要点是存储对指针的引用。为避免内存泄漏,您的类源应如下所示:

class spyder {
 public:
  spyder(std::string _name): 
    m_name(_name), finger(nullptr)
  {  }
  inline const std::string ask_name() const{
    return m_name;
  }
  std::weak_ptr<spyder> finger;
 private:
  std::string m_name;
};

【讨论】:

  • 嗨 @Ari0nhh:正如 Yksisarvinen 所说,finger(nullptr) 应该是 finger()
  • @curlywei 作为记录,在 cmets(或问题/答案)中使用单个反引号格式化 inline 代码,如下所示:`myCode` 变为 myCode。这比使用 bold 更好。
猜你喜欢
  • 1970-01-01
  • 2014-09-08
  • 2010-09-28
  • 2011-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-13
  • 2017-08-15
相关资源
最近更新 更多