Можно ли / желательно создать не копируемый аналог общего указателя (чтобы включить семантику слежения за слабым_птром / заимствованного типа)? - PullRequest
1 голос
/ 07 января 2020

Проблема: Unique_ptrs express владеет хорошо, но не может отслеживать время жизни своих объектов слабых_птров. Shared_ptrs может отслеживаться слабым_птр, но не является express владельцем явно.

Предлагаемое решение: Получить новый тип указателя (я буду называть его strong_ptr), который является просто shared_ptr, но с конструктором копирования и Оператор присваивания удален, так что его сложно клонировать. Затем мы создаем еще один новый тип loaned_ptr (который не может быть легко сохранен) для обработки временного продления времени жизни, необходимого при доступе к объекту weak_ptr, и тем самым можем избежать явного использования shared_ptr в любом месте.

Этот вопрос Не Копии владельца std :: unique_ptr и этот Лучше shared_ptr по разным типам для "владения" и "ссылки"? оба похожи, но в обоих случаях выбор оформлен как просто unique_ptr против shared_ptr и ответ не предлагает удовлетворительное решение для моего ума. (Возможно, мне следует отвечать на эти вопросы вместо того, чтобы задавать новый? Не уверен, каков правильный этикет в этом случае.)

Вот базовый c удар. Обратите внимание, что для того, чтобы пользователю слабого указателя не пришлось преобразовываться в shared_ptr для его использования, я создаю тип loaned_ptr (спасибо ржавчине за имя), который оборачивает shared_ptr, но затрудняет случайное его сохранение пользователем. Таким образом, используя по-разному производные hamstrung shared_ptr, мы можем express предполагаемое владение и направлять клиентский код в правильное использование.

#include <memory>
template <typename T>
// This owns the memory
class strong_ptr : public std::shared_ptr<T> {
public:
  strong_ptr() = default;
  strong_ptr(T* t) : std::shared_ptr<T>(t) {}
  strong_ptr(const strong_ptr&) = delete;
  strong_ptr& operator=(const strong_ptr&) = delete;
};

template <typename T>
// This can temporarily extend the lifetime but is intentionally hard to store
class borrowed_ptr : public std::shared_ptr<T> {
public:
  borrowed_ptr() = delete;
  borrowed_ptr(const borrowed_ptr&) = delete;
  borrowed_ptr& operator=(const borrowed_ptr&) = delete;

  template <typename T>
  static borrowed_ptr borrow(const std::weak_ptr<T>& wp) 
  { 
    return wp.lock();
  }
private:
  borrowed_ptr(std::shared_ptr<T> &sp) : std::shared_ptr<T>(sp) {}
};

Это кажется довольно простым и улучшением по сравнению с shared_ptr, но я не могу найти никакого обсуждения такая техника, поэтому я могу только представить, что я упустил очевидный недостаток.

Может кто-нибудь дать мне конкретную причину, почему это плохая идея? (И да, я знаю, что это менее эффективно, чем unique_ptr - для PIMPL и т. Д. Я бы по-прежнему использовал unique_ptr.)

Предостережение: я еще не использовал это больше, чем базовый c пример, но это компилируется и работает нормально:

struct xxx
{
  int yyy;
  double zzz;
};

struct aaa
{
  borrowed_ptr<xxx> naughty;
};

void testfun()
{
  strong_ptr<xxx> stp = new xxx;
  stp->yyy = 123;
  stp->zzz = 0.456;

  std::weak_ptr<xxx> wkp = stp;

//  borrowed_ptr<xxx> shp = wkp.lock(); <-- Fails to compile as planned
//  aaa badStruct { borrowed_ptr<xxx>::borrow(wkp) }; <-- Fails to compile as planned
//  aaa anotherBadStruct; <-- Fails to compile as planned
  borrowed_ptr<xxx> brp = borrowed_ptr<xxx>::borrow(wkp); // Only way to create the borrowed pointer

//  std::cout << "wkp: " << wkp->yyy << std::endl; <-- Fails to compile as planned
  std::cout << "stp: " << stp->yyy << std::endl; // ok
  std::cout << "bp: " << brp->yyy << std::endl; // ok
}

1 Ответ

4 голосов
/ 07 января 2020

Уникальное владение уникально, полная остановка. Одно место владеет этим ресурсом и освобождает его, когда этот код выбирает.

Совместное владение общим. Этот ресурс может принадлежать нескольким местам, и ресурс будет освобожден только после того, как все они это сделают. Это бинарное состояние: ресурс принадлежит одному или нескольким местам.

Ваша семантика владения уникальна ... за исключением случаев, когда это не так. И правила, которые работают определенным образом, за исключением случаев, когда они не являются проблематичными c.

Теперь ваша специфицированная реализация c полна дыр. shared/weak_ptr все явно являются частью интерфейса этих типов, поэтому исключительно легко получить shared_ptr из strong_ptr. Если у вас от weak_ptr до strong_ptr (требуется для borrowed_ptr::borrow), вы можете просто lock получить его и получить shared_ptr.

Но даже если ваш интерфейс должен был правильно скрыться все это (т. е. вы создаете свой собственный weak_ptr -эквивалентный тип и прекращаете наследовать от shared_ptr), ваш API не может остановить кого-либо для хранения этого borrowed_ptr где угодно. Да, конечно, они не могут изменить позже, но достаточно просто сохранить его в классе во время строительства или выделить кучу одного или чего-то еще.

Так что в конце день, блокирующий слабый указатель, по-прежнему представляет собой право собственности. Следовательно, семантика владения вашим стеком указателей по-прежнему shared ; есть просто поощрение API, чтобы не сохранять общее владение слишком долго.

unique_ptr не имеет «поощрения API»; имеет API принудительное исполнение . Вот что дает ему уникальную собственность. В C ++ нет механизма для создания аналогичной реализации семантики владения, которую вы хотите создать.

Поощрение может быть полезно на некотором уровне, но, вероятно, было бы так же полезно иметь borrowed_ptr, как и поощрение для тех, кто хочет express, чтобы они только временно заявляли право собственности. И просто используйте shared/weak_ptr как обычно, в противном случае. То есть ваш API должен явно распознавать, что он использует совместное владение, чтобы никто не обманулся, думая иначе.

...