【问题标题】:How does a lambda that captures a shared_ptr by value affect its use_count()?Lambda如何按值捕获Shared_ptr会影响其USE_COUNT()?
【发布时间】:2019-10-10 06:58:14
【问题描述】:

我很好奇 shared_ptr 在被 lambda 中的值捕获时的寿命。

只要 lambda 仍在内存中,我曾预计其 use_count() 始终 >= 1,但我的测试显示出一些意想不到的情况:使用计数下降到 0,然后在 lambda 主体内递增到 1...

这是我测试的:

  1. 创建一个 shared_ptr
  2. 定义一个按值捕获 shared_ptr 的 lambda
  3. 重置 shared_ptr
  4. 运行 lambda

在第 3 阶段,shared_ptr 的 use_count() 下降到 0 - 但对象没有被销毁。在第 4 阶段 - 在 lambda 内部 - use_count() 回到 1。在 lambda 运行后,use_count() 回到 0,但在 lambda 被销毁之前对象不会被销毁。

我想知道这是怎么/为什么会这样?

use_count() 不应该在 lambda 定义之后为 2,然后在 lambda 中为 1?


testing code on Repl.it:

#include <iostream>
#include <memory>

class Foo {
public:
  Foo( int v = 0 ) : val(v) {}
  ~Foo(){
    std::cout << "--- Foo destroyed ---" << std::endl;
  }
  int val = 0;
};

void logPtr( const std::shared_ptr<Foo>& p ){
    std::cout << "ptr: refs = " << p.use_count();
    if (p) {
      std::cout << ", val = " << p->val << std::endl;
    }
    else {
     std::cout << ", nullptr" << std::endl;
    }
}

int main() {

  std::shared_ptr<Foo> ptr = std::make_shared<Foo>( 0 );

  logPtr(ptr);

  std::cout << "--- define lambda ---\n";

  auto lambda = [=]() {

    std::cout << "--- run lambda ---\n";
    if (ptr) { ptr->val++; }
    logPtr(ptr);
    std::cout << "--- end lambda ---\n";

  };

  logPtr(ptr);

  std::cout << "--- reset ptr ---\n";
  ptr.reset();
  logPtr(ptr);

  // run lambda
  lambda();
  logPtr(ptr);

}

这是输出:

ptr: refs = 1, val = 0
--- define lambda ---
ptr: refs = 2, val = 0
--- reset ptr ---
ptr: refs = 0, nullptr
--- run lambda ---
ptr: refs = 1, val = 1
--- end lambda ---
ptr: refs = 0, nullptr
--- Foo destroyed ---

【问题讨论】:

  • 与 lambda 无关。这是不使用 lambda 的相同代码:repl.it/repls/LonelyDishonestBetaversion
  • 一旦您清除了 shared_ptr 对象,您就无法再获得该对象的使用计数...因为不再指向任何对象(也没有 shared_counter)。

标签: c++ c++11 lambda shared-ptr


【解决方案1】:

在 lambda 定义之后,use_count() 不应为 2

它是:

--- define lambda ---
ptr: refs = 2, val = 0

然后 1 在 lambda 内?

它是:

--- run lambda ---
ptr: refs = 1, val = 1

您感到困惑的部分与 lambdas 无关。您可以通过创建共享指针的简单副本来产生相同的效果:

  std::shared_ptr<Foo> ptr = std::make_shared<Foo>( 0 );

  logPtr( ptr );

  std::cout << "--- define lambda ---\n";

  auto cpy = ptr;

  logPtr(ptr);

  std::cout << "--- reset ptr ---\n";    
  ptr.reset();

  logPtr(ptr);

  // run "lambda"   
  {
    std::cout << "--- run lambda ---\n";
    if (cpy) {
      cpy->val++;
    }
    logPtr( cpy );
    std::cout << "--- end lambda ---\n";    
  }

  logPtr( ptr );

您似乎缺少的是reset() 的语义。作为cppreference explains,它

释放托管对象的所有权(如果有)。

这意味着

如果*this 已经拥有一个对象并且它是最后一个拥有它的shared_ptr,则该对象将通过拥有的删除器销毁。

在您的代码中,最初有两个共享指针,共享被引用对象的所有权。

ptr.reset() 之后,第一个指针不再是所有者。它被重置回null / 0。但是,第二个指针(lambda 中的副本)仍然是所有者,并保持引用的对象处于活动状态(现在 use_count 为 1)。

其余代码只检查两个不同的指针:一个仍然拥有对象,另一个不拥有任何东西。

使用原始指针的等效代码如下所示:

Foo *ptr = new Foo(0);
Foo *cpy = ptr;  // create a copy
ptr = null;      // "reset" the first pointer
logPtr(cpy);     // examine the copy
delete cpy;      // release the object through the last active pointer

【讨论】:

  • 谢谢!这很有意义。我不确定为什么我认为在调用 reset() 后我可以保留对原始指针 / use_count() 的访问。事后看来很愚蠢。
  • 我很困惑为什么 use_count 应该在 lambda 定义之后增加,因为我的理解是 lambda 只是常规函数的语法糖,所以为什么 use_count 在定义后增加它应该只在函数/lambda 调用之后增加。
  • @Kapil "因为我的理解是 lambda 只是常规函数的语法糖" - 当涉及到捕获时,lambda 更像是一个仿函数(一个结构/类operator() 已定义)而不是普通函数。在这种情况下,捕获的值是编译器为实现 lambda 而生成的函子的隐式成员。因此,当创建 lambda 并将 shared_ptr 复制到自身中时,use_count 会增加是有道理的。
  • @Remy 感谢您的解释,我现在明白为什么在 lamda 定义后 use_count 会增加
猜你喜欢
  • 1970-01-01
  • 2013-10-31
  • 2018-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-07
相关资源
最近更新 更多