按值捕获 shared_ptr 的 lambda 如何影响它的 use_count()?

How does a lambda that captures a shared_ptr by value affect its use_count()?

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

我原以为只要 lambda 仍在内存中,它的 use_count() 就始终 >= 1,但我的测试显示出一些意想不到的东西:使用计数下降到 0,然后在 lambda 内部递增到 1 body...

这是我测试的:

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

在第 3 阶段,shared_ptr 的 use_count() 降为 0 - 但 object 没有被破坏。在阶段 4 - 在 lambda 内部 - use_count() 回到 1。在 lambda 为 运行 之后,use_count() 回到 0,但是 object 直到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 ---

Shouldn't use_count() be 2 after the lambda definition

是:

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

and then 1 inside the lambda?

是:

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

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

  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,它

Releases the ownership of the managed object, if any.

这意味着

If *this already owns an object and it is the last shared_ptr owning it, the object is destroyed through the owned deleter.

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

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