Как лямбда, которая захватывает shared_ptr по значению, влияет на его use_count ()? - PullRequest
2 голосов
/ 23 мая 2019

Мне любопытно узнать о продолжительности жизни shared_ptr при захвате по значению в лямбде.

Я ожидал, что его use_count() всегда будет> = 1, пока лямбда все еще находится в памяти, но мой тест показывает нечто неожиданное: счетчик использования падает до 0, а затем увеличивается до 1 внутри тела лямбды ...

Вот что я проверял:

  1. создать shared_ptr
  2. определяет лямбду, которая захватывает shared_ptr значением
  3. сбросить shared_ptr
  4. запустить лямбду

На этапе 3 use_count() shared_ptr падает до 0 - но объект не уничтожается. На стадии 4 - внутри лямбды - use_count() возвращается к 1. После запуска лямбды use_count() возвращается к 0, но объект не уничтожается до тех пор, пока лямбда не будет уничтожена.

Мне интересно, как / почему это могло быть?

Разве use_count() не должно быть 2 после определения лямбды и затем 1 внутри лямбды?


тестирование кода на 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 ---

1 Ответ

2 голосов
/ 23 мая 2019

Не должно use_count() быть 2 после лямбда-определения

Это:

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

, а затем1 внутри лямбды?

Это:

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

Часть, в которой вы запутались, не имеет ничего общего с лямбдами.Вы можете получить тот же эффект, создав простую копию общего указателя:

  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 , он

Освобождает владельца управляемого объекта, если таковой имеется.

Что означает

Если *this уже владеет объектом, а является последним shared_ptr, владеющим им , объект уничтожается с помощью принадлежащего ему средства удаления.

В вашем коде изначально естьдва общих указателя, совместно использующих право собственности на указанный объект.

После ptr.reset() первый указатель перестает быть владельцем.Сбрасывается обратно на null / 0.Тем не менее, второй указатель (копия внутри лямбды) по-прежнему является владельцем и поддерживает ссылочный объект живым (теперь с 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
...