Должен ли я использовать std:: shared указатель для передачи указателя?
Предположим, что у меня есть объект, которым управляет std::unique_ptr
. Другие части моего кода должны получить доступ к этому объекту. Какое правильное решение для передачи указателя? Должен ли я просто передать простой указатель на std::unique_ptr::get
, или я должен использовать и передавать std::shared_ptr
вместо std::unique_ptr
вообще?
У меня есть предпочтение std::unique_ptr
, потому что владелец этого указателя фактически отвечает за очистку. Если я использую общий указатель, есть шанс, что объект останется в живых из-за общего указателя, даже если он действительно должен быть уничтожен.
EDIT: К сожалению, я забыл упомянуть, что указатель не будет просто аргументом для вызова функции, но он будет сохранен в других объектах для создания сетевой структуры объектов. Я не предпочитаю общий указатель, потому что тогда уже не ясно, кто на самом деле владеет объектом.
Ответы
Ответ 1
Если права собственности на управляемый объект не передаются (а потому, что он unique_ptr
, право собственности не может быть разделено), то более корректно отделить логику вызываемой функции от концепции собственности. Мы делаем это путем вызова по ссылке.
Это сложный способ сказать:
Дано:
std::unique_ptr<Thing> thing_ptr;
чтобы изменить Вещь:
// declaration
void doSomethingWith(Thing& thing);
// called like this
doSomethingWith(*thing_ptr);
использовать Thing без его модификации.
// declaration
void doSomethingWith(const Thing& thing);
// called like this
doSomethingWith(*thing_ptr);
Единственный раз, когда вы хотели бы упомянуть unique_ptr
в сигнатуре функции, было бы, если бы вы передавали право собственности:
// declaration
void takeMyThing(std::unique_ptr<Thing> p);
// call site
takeMyThing(std::move(thing_ptr));
Вам не нужно делать это:
void useMyThing(const std::unique_ptr<Thing>& p);
Причина, по которой это было бы плохой идеей, заключается в том, что если смущает логика useMyThing с понятием собственности, тем самым сужая возможности для повторного использования.
Рассмотрим:
useMyThing(const Thing& thing);
Thing x;
std::unique_ptr<Thing> thing_ptr = makeAThing();
useMyThing(x);
useMyThing(*thing_ptr);
Update:
Отмечаем обновление для вопроса - сохранение (не владеющих) ссылок на этот объект.
Один из способов сделать это - действительно сохранить указатель. Тем не менее, указатели страдают от возможности логической ошибки в том, что они могут юридически быть нулевыми. Другая проблема с указателями заключается в том, что они не играют хорошо с std:: algorithms
и контейнерами, требуя специальных функций сравнения и т.п.
Существует способ std::-compliant
- std::reference_wrapper<>
Итак, вместо этого:
std::vector<Thing*> my_thing_ptrs;
сделайте следующее:
std::vector<std::reference_wrapper<Thing>> my_thing_refs;
Так как std::reference_wrapper<T>
определяет оператор T&
, вы можете использовать объект reference_wrapped
в любом выражении, которое ожидало бы T
.
например:
std::unique_ptr<Thing> t1 = make_thing();
std::unique_ptr<Thing> t2 = make_thing();
std::unique_ptr<Thing> t3 = make_thing();
std::vector<std::reference_wrapper<const Thing>> thing_cache;
store_thing(*t1);
store_thing(*t2);
store_thing(*t3);
int total = 0;
for(const auto& t : thing_cache) {
total += value_of_thing(t);
}
где:
void store_thing(const Thing& t) {
thing_cache.push_back(std::cref(t));
}
int value_of_thing(const Thing& t) {
return <some calculation on t>;
}
Ответ 2
Обычно вы просто передаете ссылку или обычный указатель на другие части кода, которые хотят наблюдать за объектом.
Перейдите по ссылке:
void func(const Foo& foo);
std::unique_ptr<Foo> ptr;
// allocate ptr...
if(ptr)
func(*ptr);
Передача по необработанному указателю:
void func(const Foo* foo);
std::unique_ptr<Foo> ptr;
// allocate ptr...
func(ptr.get());
Выбор будет зависеть от необходимости передавать нулевой указатель.
Вы несете ответственность за то, чтобы наблюдатели не использовали указатель или ссылку после того, как unique_ptr
был уничтожен. Если вы не можете гарантировать это, вы должны использовать shared_ptr
вместо unique_ptr
. Наблюдатели могут содержать weak_ptr
, чтобы указать, что у них нет собственности.
РЕДАКТИРОВАТЬ: Даже если наблюдатели хотят удержать указатель или ссылку, но это делает труднее гарантировать, что он не будет использоваться после уничтожения unique_ptr
.
Ответ 3
Не сосредотачивайтесь на требованиях вызывающего абонента. Что нужно передать этой функции? Если он будет использовать объект только для продолжительности вызова, используйте ссылку.
void func(const Foo& foo);
Если ему необходимо сохранить право собственности на объект за его длительность, перейдите в shared_ptr
void func(std::shared_ptr<Foo> foo);
Ответ 4
Поскольку обновление вопроса, я бы предпочел:
-
std::reference_wrapper<>
, как было предложено Ричардом Ходжесом, если вы не требуете значения NULL.
Обратите внимание, что ваш прецедент, по-видимому, требует конструктора по умолчанию, поэтому, это не работает, если у вас нет публичного статического экземпляра для использования по умолчанию.
-
некоторый пользовательский тип not_your_pointer<T>
с требуемой семантикой: по умолчанию - конструктивен, можно копировать и присваивать, конвертировать из unique_ptr
и не иметь права собственности. Вероятно, только стоит, если вы используете его много, но, поскольку для написания нового класса интеллектуальных указателей требуется определенная мысль.
-
если вам нужно обработать оборванные ссылки изящно, используйте std::weak_ptr
и измените право собственности на shared_ptr
(то есть существует только один shared_ptr: нет никакой двусмысленности в отношении прав собственности и времени жизни объекта)
Перед обновлением вопроса я предпочел:
-
указывают, что право собственности не передается путем передачи ссылки:
void foo(T &obj); // no-one expects this to transfer ownership
называется:
foo(*ptr);
вы теряете возможность передавать nullptr
, хотя, если это имеет значение.
-
указывают, что право собственности не передается, явно запрещая его:
void foo(std::unique_ptr<T> const &p) {
// can't move or reset p to take ownership
// still get non-const access to T
}
это ясно, но выставляет политику управления памятью вызываемой стороне.
-
передать необработанный указатель и документ, в котором не должно быть передано право собственности. Это самый слабый и самый склонный к ошибкам. Не делайте этого.
Ответ 5
Эта земля уже покрыта Herb Sutter в GotW # 91 - Параметры интеллектуального указателя.
Он затрагивает и аспекты производительности, в то время как другие ответы, в то время как большие, сосредоточены на управлении памятью.
Кто-то, выше рекомендуемый прохождение умного указателя в качестве ссылки на константу, изменил свое мнение в дальнейшем редактировании. У нас был код, где это злоупотребляло - это сделало подписи "умнее", то есть более сложным, но без выгоды. Хуже всего то, что младший разработчик берет на себя - он не стал бы задавать вопросы как @Michael и "учиться", как это сделать из плохого примера. Он работает, но...
Для меня вопрос о передаче параметра интеллектуального указателя достаточно серьезен, чтобы заметно упоминаться в любой книге/статье/публикации сразу после объяснения того, что такое умные указатели.
Удивительно, что если lint-подобные программы должны отмечать передачу по ссылке const как элемент стиля.