Передача общих указателей в качестве аргументов

88

Если я объявляю объект, заключенный в общий указатель:

std::shared_ptr<myClass> myClassObject(new myClass());

затем я хотел передать его как аргумент методу:

DoSomething(myClassObject);

//the called method
void DoSomething(std::shared_ptr<myClass> arg1)
{
   arg1->someField = 4;
}

Это просто увеличивает счетчик ссылок shared_pt, и все в порядке? Или он оставляет висящий указатель?

Вы все еще должны это делать ?:

DoSomething(myClassObject.Get());

void DoSomething(std::shared_ptr<myClass>* arg1)
{
   (*arg1)->someField = 4;
}

Я думаю, что 2-й способ может быть более эффективным, потому что он должен скопировать только 1 адрес (в отличие от всего интеллектуального указателя), но 1-й способ кажется более читаемым, и я не ожидаю увеличения пределов производительности. Я просто хочу убедиться, что в этом нет ничего опасного.

Спасибо.

Стив Х
источник
14
const std::shared_ptr<myClass>& arg1
Captain Obvlious
3
Второй способ не работает, первый идиоматичен, если вам действительно нужна ваша функция для совместного владения. Но действительно DoSomethingли нужно делить собственность? Похоже, вместо этого нужно просто взять ссылку ...
ildjarn
8
@SteveH: Нет, но почему ваша функция должна навязывать особую семантику владения объекта своим вызывающим объектам, если они ей на самом деле не нужны? Ваша функция не делает ничего такого, что было бы лучше void DoSomething(myClass& arg1).
ildjarn
2
@SteveH: Вся цель умных указателей состоит в том, чтобы справляться с нестандартными проблемами владения объектами - если у вас их нет, вам вообще не следует использовать умные указатели. ; -] Что касается shared_ptr<>конкретно, вы должны передать значение, чтобы фактически разделить собственность.
ildjarn
4
Unrelated: в целом, std::make_sharedявляется не только более эффективным, но и безопаснее , чем std::shared_ptrконструктор.
Р. Мартиньо Фернандес

Ответы:

171

Я хочу передать общий указатель на функцию. Вы можете мне с этим помочь?

Конечно, я могу вам помочь. Я предполагаю, что вы имеете некоторое представление о семантике владения в C ++. Это правда?

Да, я достаточно доволен этой темой.

Хороший.

Хорошо, я могу придумать только две причины, чтобы shared_ptrпоспорить:

  1. Функция хочет разделить владение объектом;
  2. Функция выполняет некоторую операцию, которая работает конкретно с shared_ptrs.

Что вас интересует?

Я ищу общий ответ, поэтому меня действительно интересуют оба варианта. Мне любопытно, что вы имеете в виду в случае № 2.

Примеры таких функций включают std::static_pointer_castнастраиваемые компараторы или предикаты. Например, если вам нужно найти все уникальные shared_ptr из вектора, вам понадобится такой предикат.

Ах, когда функции действительно нужно манипулировать самим интеллектуальным указателем.

В яблочко.

В таком случае, я думаю, нам следует перейти по ссылке.

Да. И если он не меняет указатель, вы хотите передать по константной ссылке. Нет необходимости копировать, так как вам не нужно делить владение. Это другой сценарий.

Хорошо понял. Поговорим о другом сценарии.

Тот, где вы разделяете собственность? ОК. Как вы делите собственность с shared_ptr?

Копируя это.

Тогда функции нужно будет сделать копию shared_ptr, правильно?

Очевидно. Итак, я передаю его по ссылке на const и копирую в локальную переменную?

Нет, это пессимизация. Если он передается по ссылке, у функции не будет другого выбора, кроме как сделать копию вручную. Если он передается по значению, компилятор выберет лучший выбор между копией и перемещением и выполнит его автоматически. Итак, переходите по значению.

Хорошая точка зрения. Я должен чаще вспоминать эту статью « Хотите скорости? Передайте по значению ».

Подождите, а что, если функция хранит shared_ptr, например, в переменной-члене? Разве это не будет дублирующей копией?

Функция может просто переместить shared_ptrаргумент в свое хранилище. Перемещение a shared_ptrобходится дешево, потому что не меняет счетчик ссылок.

Ах, хорошая идея.

Но я думаю о третьем сценарии: что, если вы не хотите shared_ptrни манипулировать собственностью , ни делиться ею?

В этом случае shared_ptrсовершенно не имеет отношения к функции. Если вы хотите манипулировать указателем, возьмите указатель и позвольте вызывающим абонентам выбрать, какую семантику владения они хотят.

И брать указателя по ссылке или по стоимости?

Применяются обычные правила. Умные указатели ничего не меняют.

Передайте по значению, если я собираюсь копировать, передайте по ссылке, если я хочу избежать копирования.

Правильно.

Хм. Думаю, вы забыли еще один сценарий. Что, если я хочу разделить собственность, но только при определенных условиях?

Ах, интересный крайний случай. Я не ожидаю, что это будет происходить часто. Но когда это происходит, вы можете либо передать по значению и игнорировать копию, если она вам не нужна, либо передать по ссылке и сделать копию, если она вам нужна.

Я рискую одной дублирующей копией в первом варианте и теряю потенциальный ход во втором. Разве я не могу съесть торт и тоже?

Если вы находитесь в ситуации, когда это действительно важно, вы можете предоставить две перегрузки: одна принимает ссылку на const lvalue, а другая - ссылку на rvalue. Один копирует, другой двигается. Другой вариант - шаблон функции идеальной пересылки.

Я думаю, это охватывает все возможные сценарии. Большое спасибо.

Р. Мартиньо Фернандес
источник
2
@Jon: Зачем? Это все о я должен сделать A , или я должен сделать B . Я думаю, мы все знаем, как передавать объекты по значению / ссылке, не так ли?
sbi 07
9
Потому что проза вроде «Означает ли это, что я передам его ссылкой на const, чтобы сделать копию? Нет, это пессимизация» сбивает с толку новичка. Переход по ссылке на const не делает копию.
Джон
1
@Martinho Я не согласен с правилом «Если вы хотите манипулировать указателем, возьмите указатель». Как указано в этом ответе, отказ от временного владения объектом может быть опасным. На мой взгляд, по умолчанию должна передаваться копия во временное владение, чтобы гарантировать действительность объекта на время вызова функции.
radman
@radman Ни один сценарий в ответе, на который вы ссылаетесь, не применяет это правило, поэтому это совершенно неуместно. Напишите мне, пожалуйста, SSCCE, где вы передаете по ссылке из a shared_ptr<T> ptr;(т.е. вызываете void f(T&)с помощью f(*ptr)), и объект не переживает вызов. В качестве альтернативы напишите тот, где вы проходите по значению void f(T)и предупреждению о проблемах.
R. Martinho Fernandes
@Martinho посмотрите мой пример кода для простого автономного правильного примера. Пример предназначен для void f(T&)потоков и включает в себя потоки. Я думаю, что моя проблема в том, что тон вашего ответа препятствует передаче права собственности на shared_ptr, что является наиболее безопасным, интуитивно понятным и наименее сложным способом их использования. Я был бы категорически против того, чтобы когда-либо советовал новичку извлекать ссылку на данные, принадлежащие shared_ptr <>, возможности неправильного использования велики, а польза минимальна.
radman
22

Я думаю, что люди излишне боятся использовать необработанные указатели в качестве параметров функции. Если функция не собирается хранить указатель или иным образом влиять на его время жизни, необработанный указатель работает так же хорошо и представляет собой наименьший общий знаменатель. Рассмотрим, например, как передать a unique_ptrв функцию, которая принимает a shared_ptrв качестве параметра либо по значению, либо по константной ссылке?

void DoSomething(myClass * p);

DoSomething(myClass_shared_ptr.get());
DoSomething(myClass_unique_ptr.get());

Необработанный указатель в качестве параметра функции не мешает вам использовать интеллектуальные указатели в вызывающем коде там, где это действительно важно.

Марк Рэнсом
источник
8
Если вы используете указатель, почему бы просто не использовать ссылку? DoSomething(*a_smart_ptr)
Xeo
5
@Xeo, ты прав - так было бы даже лучше. Однако иногда вам нужно учитывать возможность использования указателя NULL.
Марк Рэнсом
1
Если у вас есть соглашение об использовании необработанных указателей в качестве выходных параметров, в вызывающем коде легче обнаружить, что переменная может измениться, например: сравнить fun(&x)с fun(x). В последнем примере xможет передаваться либо по значению, либо как const ref. Как указано выше, он также позволит вам пройти, nullptrесли вас не интересует результат ...
Андреас Магнуссон,
2
@AndreasMagnusson: это соглашение об указателе как о параметре вывода может дать ложное ощущение безопасности при работе с указателями, переданными из другого источника. Потому что в этом случае вызов - это fun (x), а * x изменяется, а в источнике нет & x, чтобы вас предупредить.
Zan Lynx
что, если вызов функции переживет объем вызывающего? Существует вероятность того, что был вызван деструктор общего ptr, и теперь функция будет пытаться получить доступ к удаленной памяти, что приведет к UB
Шридхар Тиагараджан
4

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

Я бы избегал указателя на shared_ptr <>, поскольку это противоречит цели, поскольку теперь вы снова имеете дело с raw_pointers.

R Самуэль Клатчко
источник
2

Передача по значению в вашем первом примере безопасна, но есть идиома получше. По возможности передавайте по константной ссылке - я бы сказал «да» даже при работе с интеллектуальными указателями. Ваш второй пример не совсем сломан, но очень !???. Глупо, ничего не достичь и частично лишить возможности интеллектуальных указателей и оставить вас в глючном мире боли, когда вы попытаетесь разыменовать и изменить вещи.

Джехлин
источник
3
Никто не защищает передачу по указателю, если вы имеете в виду необработанный указатель. Переход по ссылке при отсутствии разногласий по поводу прав собственности, безусловно, идиоматичен. Дело, в shared_ptr<>частности, в том, что вы должны сделать копию, чтобы фактически разделить собственность; если у вас есть ссылка или указатель на объект, shared_ptr<>то вы ничем не делитесь и подвержены тем же проблемам времени жизни, что и обычная ссылка или указатель.
ildjarn
2
@ildjarn: в этом случае можно передать постоянную ссылку. Оригинал shared_ptrвызывающего гарантированно переживет вызов функции, поэтому функция безопасна при использовании shared_ptr<> const &. Если ему нужно сохранить указатель на более позднее время, его нужно будет скопировать (на этом этапе должна быть сделана копия, а не ссылка), но нет необходимости нести расходы на копирование, если вам не нужно делать Это. В этом случае я бы пошел на постоянную ссылку ....
Дэвид Родригес - dribeas
3
@David: « Передача постоянной ссылки в этом случае - это нормально ». Ни в одном нормальном API - почему API может предписывать тип интеллектуального указателя, который он даже не использует, а не просто принимает обычную ссылку на константу ? Это почти так же плохо, как отсутствие корректности const. Если вы считаете, что технически это ничего не вредит, то я, конечно, согласен, но не думаю, что это правильно.
ildjarn
2
... если, с другой стороны, функции не нужно продлевать время жизни объекта, то вы можете просто удалить shared_ptrиз интерфейса и передать constссылку plain ( ) на указанный объект.
Дэвид Родригес - дрибес
3
@David: Что делать, если ваш потребитель API не использует shared_ptr<>с самого начала? Теперь ваш API - это просто головная боль, поскольку он вынуждает вызывающего абонента бессмысленно изменять семантику времени жизни объекта только для того, чтобы использовать его. Я не вижу в этом ничего стоящего, кроме как сказать: «Технически это ничего не вредит». Я не вижу ничего спорных о « если вам не нужно совместное владение, не использовать shared_ptr<>».
ildjarn
0

в своей функции DoSomething вы изменяете член данных экземпляра класса, myClass поэтому вы изменяете управляемый объект (необработанный указатель), а не объект (shared_ptr). Это означает, что в точке возврата этой функции все общие указатели на управляемый необработанный указатель будут видеть свой член данных: myClass::someFieldизмененный на другое значение.

в этом случае вы передаете объект функции с гарантией, что вы не изменяете его (речь идет о shared_ptr, а не о собственном объекте).

Идиома, чтобы выразить это через: const ref, например

void DoSomething(const std::shared_ptr<myClass>& arg)

Точно так же вы заверяете пользователя своей функции, что не добавляете другого владельца в список владельцев необработанного указателя. Однако вы сохранили возможность изменить базовый объект, на который указывает необработанный указатель.

ПРЕДОСТЕРЕЖЕНИЕ: это означает, что если каким-то образом кто-то вызывает shared_ptr::resetперед тем, как вы вызываете вашу функцию, и в этот момент это последний shared_ptr, владеющий raw_ptr, то ваш объект будет уничтожен, а ваша функция будет управлять свисающим указателем на уничтоженный объект. ОЧЕНЬ ОПАСНО!!!

органичанин
источник