Как weak_ptr знает, что общие ресурсы истекли?

Учитывая следующий код:

#include <memory>
#include <iostream>

using namespace std;

struct MySharedStruct
{
  int i;
};

void print_value_of_i(weak_ptr<MySharedStruct> weakPtr)
{
  if (shared_ptr<MySharedStruct> sp = weakPtr.lock())
  { cout << "Value of i = " << sp->i << endl; }
  else
  { cout << "Resource has expired"; }
}

int main()
{
  shared_ptr<MySharedStruct> sharedPtr(new MySharedStruct() );
  sharedPtr->i = 5;

  weak_ptr<MySharedStruct> weakPtr;
  weakPtr = sharedPtr;

  print_value_of_i(weakPtr);

  sharedPtr.reset(new MySharedStruct() ); // <<----- How does weak_ptr know it has expired after this line executes?
  sharedPtr->i = 10;

  print_value_of_i(weakPtr);

  return 0;
}

Как известно weak_ptr, что он истек, учитывая, что ресурс, на который ссылался shared_ptr, был существенно заменен другим ресурсом? Что означает weak_ptr, чтобы точно знать, что старый общий ресурс был уничтожен и заменен новым общим ресурсом? Примерные определения (если применимо) методов, таких как lock в weak_ptr, будут оценены.

Ответ 1

Управляющий блок, выделенный при создании shared_ptr из простого указателя, содержит как контрольный счетчик для объекта, так и указатель на сам объект и пользовательский объект удаления, если он есть. Когда этот счетчик ссылок достигает нуля, объект отпускается, и указатель имеет значение null. Таким образом, когда счетчик ссылок на объекты равен нулю, это означает, что объект ушел.

Для x86 и x86-64 они используют атомарные операции и не имеют явной блокировки (без мьютекса или спин-блокировки). Трюк реализации - это специальная функция блокировки (кодовый язык для занятости) atomic_conditional_increment, которая только увеличивает счетчик ссылок на объекты, если он не равен нулю. Он используется при реализации функции weak_ptr::lock, чтобы справиться с гонкой, когда более одного потока пытается создать shared_ptr из того же weak_ptr, где счетчик ссылок на объекты равен нулю. См. http://www.boost.org/doc/libs/1_52_0/boost/smart_ptr/detail/sp_counted_base_gcc_x86.hpp

Сам блок управления разделяется между shared_ptr и weak_ptr и имеет другой счетчик ссылок для себя, так что он остается в силе до тех пор, пока не будет выпущена последняя ссылка на него.

Когда a shared_ptr переназначается, он указывает на другой блок управления, так что блок управления только указывает на один объект. Другими словами, в контрольном блоке нет замены одного объекта другим.

Ответ 2

Короткий ответ

Я подозреваю, что большинство реализаций достигает этого, имея общий блок управления, на который ссылаются как weakPtr, так и sharedPtr. Если sharedPtr reset, он уменьшает a use_count в контрольном блоке, который может использовать weakPtr для проверки правильности указателя.

Длинный ответ

Но я думаю, что это может варьироваться в зависимости от реализации. Здесь должно произойти удар по тому, что говорит стандарт С++ 11:

shared_ptr<MySharedStruct> sharedPtr(new MySharedStruct());

В соответствии с 20.7.2.2.1, sharedPtr создается с правом собственности на данные.

weak_ptr<MySharedStruct> weakPtr;
weakPtr = sharedPtr;

В соответствии с 20.7.2.3.1, weakPtr создается, а затем присваивается значение sharedPtr. После назначения weakPtr и sharedPtr теперь разделяют владение данными.

sharedPtr.reset(new MySharedStruct());

В соответствии с 20.7.2.2.4, reset(Y*) эквивалентно shared_ptr(Y*).swap(*this). Другими словами, sharedPtr заменяет свое содержимое временным shared_ptr, которому принадлежат новые данные.

После свопинга sharedPtr будет принадлежать новым данным, а временное разделяет владение старыми данными с помощью weakPtr.

В соответствии с 20.7.2.2.2 временное уничтожается:

  • Так как временный владелец старых данных и не разделяет это право собственности с другим экземпляром shared_ptr, он удаляет старые данные.
  • Все экземпляры, которые разделяют право владения с временным shared_ptr, будут сообщать о use_count(), который меньше, чем его предыдущее значение.

Это означает, что weakPtr.use_count() == 0.

if (shared_ptr<MySharedStruct> sp = weakPtr.lock()) { 
  cout << "Value of i = " << sp->i << endl;
} else {
  cout << "Resource has expired"; 
}

В соответствии с 20.7.2.3.5 вызов lock эквивалентен

expired() ? shared_ptr<T>() : shared_ptr<T>(*this)

... и expired() эквивалентно

use_count() == 0

... что означает, что lock вернет пустой shared_ptr.