Я просто смотрел трансляции "Going Native 2012" и заметил обсуждение std::shared_ptr
. Я был немного удивлен, услышав несколько отрицательный взгляд Бьярне на std::shared_ptr
его комментарий о том, что его следует использовать в качестве «последнего средства», когда время жизни объекта не определено (что, я считаю, по его мнению, должно быть нечастым случаем).
Кто-нибудь захочет объяснить это немного глубже? Как мы можем программировать без std::shared_ptr
и до сих пор управлять объектные жизненным раз в безопасном пути?
c++
c++11
smart-pointer
ronag
источник
источник
Ответы:
Если вы сможете избежать совместного владения, то ваше приложение будет проще и понятнее и, следовательно, менее подвержено ошибкам, возникающим во время обслуживания. Сложные или неясные модели владения, как правило, приводят к тому, что трудно проследить связи различных частей приложения через общее состояние, которое может быть нелегко отследить.
Учитывая это, предпочтительнее использовать объекты с автоматическим сроком хранения и иметь «объекты» подобъектов. В противном случае это
unique_ptr
может быть хорошей альтернативойshared_ptr
, если не последним средством, каким-то образом попасть в список желательных инструментов.источник
Мир, в котором живет Бьярне, очень ... академичен, из-за отсутствия лучшего термина. Если ваш код может быть спроектирован и структурирован так, что объекты имеют очень преднамеренные реляционные иерархии, так что отношения владения являются жесткими и несгибаемыми, код перемещается в одном направлении (от высокого уровня к низкому уровню), и объекты общаются только с теми, кто ниже. иерархии, то вы не найдете особой необходимости
shared_ptr
. Это то, что вы используете в тех редких случаях, когда кто-то должен нарушать правила. Но в противном случае вы можете просто вставить все вvector
s или другие структуры данных, которые используют семантику значений, иunique_ptr
s для вещей, которые вы должны выделять отдельно.Хотя это великий мир для жизни, это не то, чем ты можешь заниматься все время. Если вы не можете организовать свой код таким образом, потому что дизайн системы, которую вы пытаетесь создать, означает, что это невозможно (или просто крайне неприятно), тогда вы обнаружите, что вам все больше и больше нужно совместно владеть объектами. ,
В такой системе держать голые указатели ... не совсем точно, но это вызывает вопросы. Самое замечательное в том,
shared_ptr
что он предоставляет разумные синтаксические гарантии о времени жизни объекта. Это может быть сломано? Конечно. Но люди могут иconst_cast
вещи; базовый уход и питаниеshared_ptr
должны обеспечивать разумное качество жизни для выделенных объектов, права собственности на которые должны быть разделены.Тогда есть
weak_ptr
s, которые нельзя использовать в отсутствие ashared_ptr
. Если ваша система жестко структурирована, вы можете хранить голый указатель на некоторый объект, будучи уверенным в том, что структура приложения гарантирует, что указанный объект переживет вас. Вы можете вызвать функцию, которая возвращает указатель на некоторое внутреннее или внешнее значение (например, найти объект с именем X). В правильно структурированном коде эта функция будет доступна вам только в том случае, если время жизни объекта будет гарантированно превышать ваше; таким образом, хранение этого голого указателя в вашем объекте - это хорошо.Поскольку такой жесткости не всегда удается достичь в реальных системах, вам нужен какой-то способ разумного обеспечения срока службы. Иногда вам не нужно полное владение; иногда вам просто нужно знать, когда указатель плох или хорош. Вот где
weak_ptr
приходит. Были случаи, когда я мог использоватьunique_ptr
илиboost::scoped_ptr
, но я должен был использовать,shared_ptr
потому что мне специально нужно было дать кому-то «изменчивый» указатель. Указатель, время жизни которого было неопределенным, и они могли запрашивать, когда этот указатель был уничтожен.Безопасный способ выжить, когда состояние мира неопределенно.
Может ли это быть сделано с помощью вызова некоторой функции, чтобы получить указатель, а не через
weak_ptr
? Да, но это может быть легко сломано. Функция, которая возвращает голый указатель, не имеет возможности синтаксически предлагать пользователю не делать что-то вроде сохранения этого указателя в долгосрочной перспективе. Возвращениеshared_ptr
также делает слишком простым его хранение и потенциально продлевает срок службы объекта. Возвращение,weak_ptr
однако, убедительно говорит о том, что хранение полученнойshared_ptr
вами информацииlock
является ... сомнительной идеей. Это не помешает вам сделать это, но ничто в C ++ не мешает вам взломать код.weak_ptr
обеспечивает некоторое минимальное сопротивление от выполнения естественных вещей.Это не значит, что
shared_ptr
нельзя злоупотреблять ; это конечно может. Прежде всегоunique_ptr
, было много случаев, когда я просто использовал a,boost::shared_ptr
потому что мне нужно было передать указатель RAII или поместить его в список. Без ходовой семантики иunique_ptr
,boost::shared_ptr
был единственным реальным решением.И вы можете использовать его в местах, где это совершенно не нужно. Как указано выше, правильная структура кода может исключить необходимость использования
shared_ptr
. Но если ваша система не может быть структурирована как таковая и все еще делать то, что ей нужно, онаshared_ptr
будет весьма полезна.источник
shared_ptr
отлично подходит для систем, где c ++ интегрирован с языком сценариев, таким как python. Использованиеboost::python
, подсчет ссылок на стороне c ++ и python сильно взаимодействует; любой объект из c ++ все еще может быть в Python, и он не умрет.shared_ptr
. Оба используют свои собственные реализацииintrusive_ptr
. Я привожу это только потому, что они оба являются реальными примерами больших приложений, написанных на C ++shared_ptr
равной степени относится кintrusive_ptr
: он возражает против всей концепции совместной собственности, а не против какого-либо конкретного написания этой концепции. Таким образом, для целей этого вопроса это два реальных примера больших приложений, которые действительно используютshared_ptr
. (Более того, они демонстрируют, чтоshared_ptr
это полезно, даже если оно неweak_ptr
Я не верю, что когда-либо использовал
std::shared_ptr
.Большую часть времени объект ассоциируется с какой-либо коллекцией, к которой он принадлежит на протяжении всего своего жизненного цикла. В этом случае вы можете просто использовать
whatever_collection<o_type>
илиwhatever_collection<std::unique_ptr<o_type>>
, эта коллекция является членом объекта или автоматической переменной. Конечно, если вам не нужно динамическое количество объектов, вы можете просто использовать автоматический массив фиксированного размера.Ни для итерации по коллекции, ни для какой-либо другой операции над объектом не требуется вспомогательная функция для разделения владения ... она использует объект, затем возвращает его, и вызывающая сторона гарантирует, что объект останется в живых в течение всего вызова . Это, безусловно, наиболее используемый контракт между вызывающим абонентом и вызываемым абонентом.
Никол Болас прокомментировал, что «Если какой-то объект держится за голый указатель, и этот объект умирает ... упс». и «Объекты должны гарантировать, что объект живет через жизнь этого объекта. Только
shared_ptr
может сделать это».Я не покупаю этот аргумент. По крайней мере, это не
shared_ptr
решает эту проблему. Что о:Как и при сборке мусора, использование по умолчанию
shared_ptr
заставляет программиста не думать о контракте между объектами или между функцией и вызывающей стороной. Нужно думать о правильных предусловиях и постусловиях, а время жизни объекта - всего лишь маленький кусочек этого большего пирога.Объекты не «умирают», часть кода уничтожает их. И бросать
shared_ptr
на проблему вместо того, чтобы выяснить контракт вызова, является ложной безопасностью.источник
shared_ptr
иweak_ptr
были разработаны, чтобы избежать. Бьярне пытается жить в мире, где у всего есть хорошая, явная жизнь, и все построено вокруг этого. И если ты сможешь построить этот мир, прекрасно. Но это не так, как в реальном мире. Объекты должны гарантировать, что объект живет в течение жизни этого объекта. Толькоshared_ptr
могу сделать это.shared_ptr
смягчает только одну конкретную внешнюю модификацию, и даже не самую распространенную. И это не обязанность объекта гарантировать правильность его времени жизни, если в контракте вызова функции указано иное.unique_ptr
, выражающий, что существует только один указатель на объект, и он имеет владельца.shared_ptr
, он все равно должен вернуть aunique_ptr
. Преобразование изunique_ptr
вshared_ptr
легко, но обратное логически невозможно.Я предпочитаю думать не в абсолютных терминах (например, «последнее средство»), а относительно проблемной области.
C ++ может предложить несколько различных способов управления временем жизни. Некоторые из них пытаются переместить объекты в стеке. Некоторые другие пытаются обойти это ограничение. Некоторые из них являются «буквальными», другие - приблизительными.
На самом деле вы можете:
Person
имеющие одно и то же,name
являются одним и тем же человеком (лучше: два представления одного и того же человека ). Срок службы предоставляется машинным стеком, и конец - по существу - не имеет значения для программы (поскольку человек - это его имя , независимо от того, чтоPerson
его несет)std::unique_ptr
делает wat (вы можете представить это как вектор с размером 1). Опять же, вы допускаете, что объект начинает существовать (и прекращает свое существование) до (после) структуры данных, к которой он обращается.Слабость этого метода заключается в том, что типы и количества объектов не могут изменяться во время выполнения вызовов более глубокого уровня стека относительно места их создания. Все эти методы "не справляются" со своей силой во всех ситуациях, когда создание и удаление объекта являются следствием действий пользователя, так что тип времени выполнения объекта неизвестен во время компиляции, и могут быть избыточные структуры, ссылающиеся на объекты пользователь просит удалить из более глубокого вызова функции уровня стека. В этом случае вам необходимо:
C ++ isteslf не имеет никакого собственного механизма для мониторинга этого события (
while(are_they_needed)
), поэтому вы должны приблизиться к:Переходя к самому первому решению последнего, объем вспомогательной структуры данных, необходимый для управления временем жизни объекта, увеличивается, так как время, затрачиваемое на его организацию и обслуживание.
У сборщика мусора есть стоимость, у shared_ptr меньше, unique_ptr еще меньше, а у управляемых объектов стека очень мало.
Является ли
shared_ptr
«последним средством»? Нет, это не так: последнее средство - сборщики мусора.shared_ptr
на самом делеstd::
предлагается последнее средство. Но может быть правильным решением, если вы находитесь в ситуации, которую я объяснил.источник
Одна вещь, упомянутая Хербом Саттером в более позднем сеансе, заключается в том, что каждый раз, когда вы копируете a
shared_ptr<>
, возникает взаимосвязанный шаг / уменьшение, который должен произойти. В многопоточном коде в многоядерной системе синхронизация памяти не является незначительной. Учитывая выбор, лучше использовать либо значение стека, либо aunique_ptr<>
и передавать ссылки или необработанные указатели.источник
shared_ptr
ссылку lvalue или rvalue ...shared_ptr
как серебряную пулю, которая решит все проблемы с утечкой памяти только потому, что она в стандарте. Это заманчивая ловушка, но все же важно знать о владении ресурсами, и если это владение не является общим, тоshared_ptr<>
это не лучший вариант.Я не помню, было ли последнее слово «курорт» точным словом, которое он использовал, но я считаю, что реальное значение того, что он сказал, было последним «выбором»: учитывая четкие условия собственности; Unique_ptr, weak_ptr, shared_ptr и даже голые указатели имеют свое место.
Они все согласились с тем, что мы (разработчики, авторы книг и т. Д.) Все в «фазе изучения» C ++ 11, и шаблоны и стили определяются.
В качестве примера, пояснил Херб, нам следует ожидать появления новых выпусков некоторых оригинальных книг по С ++, таких как Effective C ++ (Meyers) и C ++ Coding Standards (Sutter & Alexandrescu), через пару лет, пока опыт отрасли и лучшие практики с C ++ 11 показывает.
источник
Я думаю, что он имеет в виду, что для всех становится обычным писать shared_ptr всякий раз, когда они могут написать стандартный указатель (например, своего рода глобальную замену), и что он используется как отговорка вместо того, чтобы фактически проектировать или, по крайней мере, планирование создания и удаления объектов.
Еще одна вещь, которую люди забывают (помимо узких мест блокировки / обновления / разблокировки, упомянутых в материале выше), это то, что один shared_ptr не решает проблемы цикла. Вы все еще можете утечь ресурсы с помощью shared_ptr:
Объект A содержит общий указатель на другой объект A. Объект B создает A a1 и A a2 и назначает a1.otherA = a2; и a2.otherA = a1; Теперь, общие указатели объекта B, которые он использовал для создания a1, a2, выходят из области видимости (скажем, в конце функции). Теперь у вас есть утечка - никто больше не ссылается на a1 и a2, но они ссылаются друг на друга, поэтому их количество ссылок всегда равно 1, и вы просочились.
Это простой пример, когда это происходит в реальном коде, обычно это происходит сложными способами. Существует решение со слабым_птром, но многие сейчас просто используют shared_ptr везде и даже не знают о проблеме утечки или даже слабого_птр.
Подводя итог: я думаю, что комментарии, на которые ссылается ФП, сводятся к следующему:
Независимо от того, на каком языке вы работаете (управляемый, неуправляемый или что-то промежуточное с подсчетом ссылок, например shared_ptr), вы должны понимать и намеренно принимать решение о создании объекта, времени его жизни и разрушении.
редактировать: даже если это означает «неизвестно, мне нужно использовать shared_ptr», вы все еще думаете об этом и делаете это намеренно.
источник
Я отвечу из моего опыта с Objective-C, языком, где все объекты подсчитываются и распределяются в куче. Из-за наличия одного способа обработки объектов программисту намного проще. Это позволило определить стандартные правила, которые при соблюдении гарантируют надежность кода и отсутствие утечек памяти. Это также сделало возможным появление умных оптимизаций компилятора, таких как недавний ARC (автоматический подсчет ссылок).
Я хочу сказать, что shared_ptr должен быть вашим первым вариантом, а не последним средством. Используйте подсчет ссылок по умолчанию и другие параметры, только если вы уверены в том, что делаете. Вы будете более продуктивными, а ваш код - более надежным.
источник
Я постараюсь ответить на вопрос:
C ++ имеет большое количество различных способов сделать память, например:
struct A { MyStruct s1,s2; };
вместо shared_ptr в области видимости. Это только для опытных программистов, потому что это требует, чтобы вы понимали, как работают зависимости, и требовало способности контролировать зависимости, достаточные для того, чтобы ограничить их деревом. Порядок классов в заголовочном файле является важным аспектом этого. Кажется, что это использование уже распространено в встроенных типах c ++, но его использование с классами, определенными программистом, кажется менее используемым из-за проблем с зависимостями и порядком классов. У этого решения также есть проблемы с sizeof. Программисты видят в этом проблемы как требование использовать предварительные объявления или ненужные #include, и поэтому многие программисты прибегнут к низкому решению указателей, а затем к shared_ptr.MyClass &find_obj(int i);
+ clone () вместоshared_ptr<MyClass> create_obj(int i);
. Многие программисты хотят создавать фабрики для создания новых объектов. shared_ptr идеально подходит для такого использования. Проблема заключается в том, что оно уже предполагает сложное решение для управления памятью с использованием распределения кучи / свободного хранилища вместо более простого стекового или объектного решения. Хорошая иерархия классов C ++ поддерживает все схемы управления памятью, а не только одну из них. Решение на основе ссылок может работать, если возвращаемый объект хранится внутри содержащего объекта, вместо использования локальной переменной области действия функции. Следует избегать передачи права собственности от фабрики к пользовательскому коду. Копирование объекта после использования find_obj () - это хороший способ справиться с этим - это могут обработать обычные конструкторы копирования и нормальный конструктор (другого класса) с параметром refrerence или clone () для полиморфных объектов.источник
unique_ptr
лучше всего подходит для заводов. Вы можете превратитьunique_ptr
вshared_ptr
, но логически невозможно пойти в другом направлении.