Использование std:: reference_wrapper <T> в контейнере

Если бы я мог удалить все исходные указатели * из моего кода, потому что использование их может быть небезопасным потоком, а намерения дизайна не ясны (необязательное значение, право собственности и т.д.). Иногда, однако, не так просто не использовать указатели. Например, мы склонны использовать указатели для базового типа в контейнере полиморфных типов:

class A : noncopyable { ... };
class B : public A { ... };

std::vector<A*> v;
v.emplace_back(new B);

// temporary container for some operation
std::vector<A*> selected;
if(check())
   selected.emplace_back(v.front());

Что вы можете сказать о коде выше? Кто является владельцем? Это общая собственность или нет? Именно поэтому мы должны сделать это для v:

std::vector<std::unique_ptr<A>> v;
v.emplace_back(make_unique<B>());

Теперь ясно, что v владеет объектами, но мне все еще не нравится, что selected имеет необработанный указатель и делает мой дизайн неинтуитивным. В стандартной библиотеке С++ я считаю, что есть только один тип, который мог бы выполнить эту работу - std:: reference_wrapper:

std::vector<std::unique_ptr<A>> v;
v.emplace_back(make_unique<B>());

// temporary container for some operation
std::vector<std::reference_wrapper<A>> selected;
if(check())
  selected.emplace_back(*v.front());

Как вы относитесь к этому коду? Это хорошая практика? Я знаю, что std::ref() и std::cref, где предназначено, в первую очередь, работать с шаблонами, но кажется, что здесь мы также можем использовать его, чтобы четко сформулировать наше намерение дизайна. Единственная проблема, которую я вижу, заключается в том, что мне нужно разыгрывать std::reference_wrapper с помощью get(), а внутри operator*() или operator->() нет такого интерфейса, как в контейнере с unique_ptr. Должен ли я писать что-то подобное самостоятельно? Или, возможно, reference_wrapper может быть расширен для такого варианта использования в будущих версиях С++? Пожалуйста, поделитесь своими отзывами.

EDIT: я изменил образцы кода, чтобы лучше показать намерение.

Ответ 1

Вы уже предоставили решение, которое выглядит здорово. Я понимаю, что вопрос: "Как вы себя чувствуете?"

Мое личное чувство заключается в том, что с одной стороны и между собой необходимо соблюдать равновесие между безопасностью и однозначностью, а с другой - простотой кода. Похоже, ваше решение может слишком сильно подтолкнуть его к безопасности и слишком сильно снизить простоту. Всякий раз, когда я использовал контейнеры, содержащие "слабые ссылки", я использовал исходные указатели для их представления. Правда, это может сделать менее понятным, кто является владельцем объекта, но он имеет некоторые преимущества: вам не нужно изучать, что такое "reference_wrapper", и код ясен. Если вы используете их (контейнер с слабыми ссылками) только временно и инкапсулируете это использование, проблема владения должна быть минимальной.

Но это только вопрос личных предпочтений, я думаю. Позвольте мне просто предложить разные типы для той же цели. Это обеспечивается тем, что вы можете позволить себе использовать Boost. Для "сильных" ссылок (которые владеют ресурсом) вы можете использовать библиотеку Steve Watanabe Type Erasure. Он не требует явного использования свободной памяти, и я предполагаю, что для небольших типов он может полностью отказаться от использования кучи памяти (с использованием оптимизации малого буфера). Он был недавно принят в Boost, хотя он еще не был выпущен, я думаю.

Для слабых ссылок рассмотрите использование "дополнительных ссылок" с помощью Boost.Optional:

int i = 0;
boost::optional<int&> oi = i; // note: int&
i = 2;
assert(*oi == 2);

Он имеет ту же семантику, что и reference_wrapper.

Ответ 2

Я думаю, что их вызов shared_ptr не является логически неправильным. Однако, глядя на определение std:: weak_ptr:

std:: weak_ptr - это умный указатель, который содержит не-владеющий ( "слабый" ) ссылка на объект, который управляется std:: shared_ptr. Это должно быть преобразован в std:: shared_ptr, чтобы получить доступ к указанному объекту.

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