Итак, я сделал (небольшое количество) чтение и знаю, что unique_ptr
в сочетании с необработанными указателями это шаблон, который можно использовать при моделировании уникального владельца.
Тем не менее, мне очень нравится простая и понятная концепция использования weak_ptr, чтобы проверить, является ли значение допустимым, а затем отбросить shared_ptr после его использования, и это радует всех (при небольшом снижении производительности подсчета ссылок).
Моя конкретная проблема сейчас заключается в создании выразительной и гибкой системы для отслеживания точек с несколькими касаниями, и было элегантно использовать уничтожение объекта, представляющего прикосновение, в качестве сигнала о завершении касания. Если бы я пошел с необработанным маршрутом указателя, мне нужно было бы определить некоторую семантику, которой должен был бы соответствовать каждый компонент, взаимодействующий с этой системой, что-то немного уродливое, например, наличие второго аргумента, который указывает, действителен ли указатель или что-то подобное.
Эта проблема с маршрутом необработанного указателя, возможно, является проблемой, так как я не ожидаю, что это станет большим проектом, но вопрос в основном представляет практический интерес с точки зрения того, как написать лучший современный код C ++.
псевдокод:
class InputConsumer {
void handle(std::list<std::weak_ptr<Touch>>*);
// consumer doesnt hold references to anything outside of its concern.
// It only has to know how to deal with input data made available to it.
// the consumer is a child who is given toys to play with and I am trying to
// see how far I can go to sandbox it
}
class InputSender {
std::list<std::weak_ptr<Touch>> exposedinputdata;
std::list<std::shared_ptr<Touch>> therealownedtouches;
// sender populates exposedinputdata when input events come in.
// I want to let the consumer copy out weak_ptrs as much as it wants,
// but for it to never hold on to it indefinitely. There does not appear
// to be an easy way to enforce this (admittedly it is kind of vague. it
// has to be around for long enough to be used to read out data, but
// not e.g. 3 frames. Maybe what I need is to make an intelligent
// smart pointer that has a timer inside of it.)
std::list<std::weak_ptr<InputConsumer>> consumers;
void feedConsumersWithInput() {
for (auto i = consumers.begin(); i != consumers.end(); ++i) {
if (i->expired()) {
consumers.erase(i);
} else {
i->lock()->handle(&exposedinputdata);
}
}
}
Когда я увидел способность weak_ptr
чтобы выразить семантику, очень похожую на то, что я моделирую, я просто очень хотел ее использовать, потому что ее интерфейс прост и понятен, и что наиболее важно, он самодокументирует, как этот код будет работать. Это огромное преимущество в будущем.
Теперь я почти уверен, что все будет очень хорошо до тех пор, пока InputConsumer
звонки lock()
на weak_ptr<Touch>
и сохраняет shared_ptr<Touch>
, Это предотвратит Touch
от освобождения даже после того, как его основной владелец стер shared_ptr
! Мне кажется, это единственная морщинка, причем маленькая. Я думаю, что гораздо сложнее испортить обработку владения с помощью shared_ptr, чем с необработанными указателями.
Какие есть способы исправить это? Я подумываю о создании подкласса шаблона (?! Я никогда не писал такую вещь, недавно вошел в шаблоны. Люблю их) из weak_ptr, который каким-то образом запрещает сохранять shared_ptr, или что-то в этом роде.
Может быть, я могу подкласс shared_ptr
и переопределить его dtor, чтобы бросить, если он не вызывает удаление?
Учитывая, что имея weak_ptr
всегда требует подсчета ссылок, развертывания любого решения (более или менее), как переписывание shared_ptr
один.
Быстрый и грязный способ, вероятно, является производным shared_ptr и предоставление ему только ctor хода (monitore_ptr(monitored_ptr&&)
) и оператор передачи (monitored_ptr& operator=(monitored_ptr&&)
), таким образом отключая shared_ptr
копировать (и, следовательно,разделениевозможности.
Проблема деривации заключается в том, что, будучи shared_ptr
не полиморфный, в итоге вы получите не полиморфный тип, который проявляет некоторый полиморфизм к shared_ptr (вы можете присвоить ему, тем самым нарушая свои предположения).
Это можно компенсировать, используя защищенное наследование и повторно раскрывая только необходимые функции (по существу, *
а также -
> операторы).
Чтобы избежать неправильного поведения против weak_ptr
(как ваш monitored_ptr
дано weak_ptr
дано shared_ptr
) … я тоже предлагаю переопределить weak_ptr
а также с защищенным наследованием.
На этом этапе вы получите пару классов, которые являются самодостаточными и несовместимыми с любым другим указателем общего доступа.
В любом случае, ключом является написание правильных конструкторов, а не (как вы предложили) добавление деструктора: это ситуация с большим потенциалом, трудно управляемым.
(см. например Вот)
Я собираюсь предложить довольно простой дизайн. Это тонкая обертка вокруг weak_ptr
где единственный способ получить доступ к базовому T
это передать лямбду в метод.
Это ограничивает срок службы shared_ptr
от lock()
время вызова метода: теоретически вы можете заблокировать shared_ptr
до бесконечности, вы можете сделать это только никогда не возвращаясь из try
,
template<typename T>
struct monitored_pointer {
template<typename Lambda>
bool try( Lambda&& closure ) const {
auto p = m_ptr.lock();
if (!p)
return false;
std::forward<Lambda>(closure)(*p):
return true;
}
bool valid() const {
return try( [](T&){} );
}
void reset( std::weak_ptr<T> ptr = std::weak_ptr<T>() )
{
m_ptr = ptr;
}
explicit operator bool() const { return valid(); }
monitored_pointer() {}
monitored_pointer( monitored_pointer && ) = default;
monitored_pointer& operator=( monitored_pointer && ) = default;
explicit monitored_pointer( std::weak_ptr<T> ptr ):m_ptr(ptr) {}
private:
std::weak_ptr<T> m_ptr;
};
valid
а также operator bool
просто помогает когда хочешь убрать истекший monitored_pointer
s.
Использование выглядит примерно так:
if (!ptr.try( [&]( Touch& touch ) {
// code that uses the `touch` here
})) {
// code that handles the fact that ptr is no longer valid here
}