Подтвердить что ты не робот

Является ли совместное владение объектами признаком плохого дизайна?

Фон: при чтении Dr. Stroustrup и часто задаваемые вопросы, я замечаю некоторые сильные "мнения" и отличные советы от легендарного ученого и программиста CS. Один из них - shared_ptr в С++ 0x. Он начинает объяснять о shared_ptr и о том, как он представляет совместное владение заостренным объектом. На последней строке он говорит, и я цитирую:

. A shared_ptr представляет общий владение, но совместная собственность не мой идеал: лучше, если объект имеет определенного владельца и определенного, прогнозируемый срок службы.

Мой вопрос. Насколько RAII заменяет другие шаблоны проектирования, такие как Garbage Collection? Я предполагаю, что ручное управление памятью не используется для представления совместного использования в системе.

4b9b3361

Ответ 1

В какой степени RAII заменяет другие шаблоны проектирования, такие как Garbage Collection? Я предполагаю, что ручное управление памятью не используется для представления совместного владения в системе.

Хм, с GC, вам действительно не нужно думать о собственности. Объект остается вокруг, пока кому-то это нужно. Совместное владение - это по умолчанию и единственный выбор.

И, конечно же, все может быть сделано с долевым участием. Но это иногда приводит к очень неуклюжим коду, потому что вы не можете управлять или ограничивать время жизни объекта. Вы должны использовать блоки С# using или try/finally с закрытием/удалением вызовов в предложении finally, чтобы убедиться, что объект очищается, когда он выходит из области видимости.

В этих случаях RAII намного лучше подходит: когда объект выходит из области видимости, вся очистка должна выполняться автоматически.

RAII в значительной степени заменяет GC. 99% времени, совместное владение не совсем то, что вы хотите в идеале. Это приемлемый компромисс, в обмен на спасение многих головных болей путем сбора сборщика мусора, но он не совсем соответствует тому, что вы хотите. Вы хотите, чтобы ресурс мог умереть в какой-то момент. Не раньше, а не после. Когда RAII является опцией, это приводит к более элегантному, сжатому и надежному коду в этих случаях.

RAII не идеальна. В основном потому, что это не так хорошо, когда случай, когда вы просто не знаете время жизни объекта. Он должен оставаться в течение долгого времени, пока кто-либо его использует. Но вы не хотите держать его навсегда (или до тех пор, пока область вокруг всех клиентов, которая может быть просто главной функцией).

В этих случаях пользователи С++ должны "понижать" до семантики совместного использования, обычно реализуемой путем подсчета ссылок через shared_ptr. И в этом случае GC выигрывает. Он может реализовать совместное владение гораздо более надежным способом (например, для обработки циклов) и более эффективно (амортизированная стоимость пересчета считается огромной по сравнению с приличным GC)

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

Ответ 2

Задача программиста состоит в том, чтобы выразить вещи элегантно на своем языке выбора.

С++ имеет очень хорошую семантику для построения и уничтожения объектов в стеке. Если ресурс может быть выделен на время блока области видимости, то хороший программист, вероятно, займет тот путь наименьшего сопротивления. Время жизни объекта ограничено фигурными скобками, которые, вероятно, уже есть.

Если нет хорошего способа разместить объект непосредственно в стеке, возможно, он может быть помещен внутри другого объекта в качестве члена. Теперь его жизнь немного длиннее, но С++ все еще много. Время жизни объекта ограничено родительским объектом - проблема делегирована.

Однако может быть не один родитель. Следующее лучшее - это последовательность приемных родителей. Для этого используется auto_ptr. Все еще очень хорошо, потому что программист должен знать, какой именно родитель является владельцем. Время жизни объекта ограничено временем жизни его последователей. Один шаг вниз по цепочке детерминизма и самой элегантности shared_ptr: продолжительность жизни ограничена объединением пула владельцев.

Но, возможно, этот ресурс не совпадает с каким-либо другим объектом, множеством объектов или потоком управления в системе. Это было создано на каком-то событии, которое произошло и уничтожено на другом мероприятии. Хотя существует множество инструментов для разграничения времени жизни делегациями и другими сроками службы, их недостаточно для вычисления любой произвольной функции. Поэтому программист может решить написать функцию нескольких переменных, чтобы определить, возникает ли объект или исчезает объект, и вызывают new и delete.

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


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

Ответ 3

Является ли сбор мусора образцом дизайна? Я не знаю.

Большим преимуществом совместного владения является его неотъемлемая предсказуемость. С GC рекультивация ресурсов из ваших рук. В этом-то и дело. Когда и как это происходит, обычно не на уме разработчика, использующего его. При совместном владении вы управляете (будьте осторожны, иногда слишком много контроля - это плохо). Допустим, ваше приложение выдает миллион shared_ptr на X. Все это вы делаете, вы несете ответственность за них, и у вас есть полный контроль над тем, когда эти ссылки создаются и уничтожаются. Таким образом, решительный и осторожный программист должен знать обозреватель, который ссылается на то, что и как долго. Если вы хотите, чтобы объект был уничтожен, вам нужно уничтожить все общие ссылки на него, а viola - нет.

Это несет некоторые глубокие последствия для людей, которые делают программное обеспечение реального времени, которое ДОЛЖНО быть полностью предсказуемым. Это также означает, что вы можете подкупать способами, которые выглядят ужасно, как утечка памяти. Я лично не хочу быть решительным и осторожным программистом, когда мне не нужно быть (идти вперед и смеяться, я хочу пойти на пикники и велосипедные прогулки, а не считать мои ссылки), поэтому, где мне подходит GC маршрут. Я написал немного звукового программного обеспечения в реальном времени и использовал общие ссылки для управления ресурсами.

Ваш вопрос: Когда RAII терпит неудачу? (В контексте общих ссылок) Ответ: Когда вы не можете ответить на вопрос: кто может иметь ссылку на это? Когда появляются злобные безвкусные круги собственности.

Мой вопрос: Когда GC не работает? Мой ответ: когда вам нужен полный контроль и предсказуемость. Когда GC является написанной Sun Microsystems в последнюю минуту, тире до крайнего срока и имеет смехотворное поведение, которое могло быть спроектировано и реализовано только сильно пьяными обезьянами протохуманного кода, заимствованными у Microsoft.

Мое мнение: Я думаю, что BS действительно серьезно относится к четкому дизайну. Кажется очевидным, что наличие одного места, где будут уничтожены ресурсы, обычно более четкое, чем у многих мест, где они могут быть уничтожены.

Ответ 4

Мой вопрос: в какой степени RAII заменяет другие шаблоны проектирования как сбор мусора? Я предполагаю, что ручное управление памятью не используется для представления совместного использования в системе.

Я не уверен в том, чтобы называть это шаблоном проектирования, но в моем столь же сильном мнении и просто о ресурсах памяти RAII решает почти все проблемы, которые GC может решить, вводя меньше.

Является ли совместное владение объектами признаком плохого дизайна?

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

Самая большая проблема, которую я нахожу в GC или просто совместном владении в целом, заключается в том, что она не освобождает разработчика от каких-либо обязанностей, когда дело касается ресурсов приложений, но может дать иллюзию этого. Если у нас есть такой случай (Scene является единственным логическим владельцем ресурса, но другие вещи содержат ссылку/указатель на него, как камера, хранящая список исключений сцены, определенный пользователем для исключения из рендеринга):

введите описание изображения здесь

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

Без GC мы можем удалить его из списка сцен и разрешить его деструктор вызываться, вызывая событие, позволяющее Thing1, Thing2 и Thing3 установить для них указатели на нуль или удалить их из списка, чтобы у них не было висящих указателей.

С GC, это в основном то же самое. Мы удаляем ресурс из списка сцен при запуске события, чтобы позволить Thing1, Thing2 и Thing3 установить ссылки на нуль или удалить их из списка, чтобы сборщик мусора мог его собрать.

Ошибка молчаливого программиста, которая летает под радаром

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

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

введите описание изображения здесь

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

Слабые ссылки

Слабые ссылки концептуально идеальны для меня для Thing1, Thing2 и Thing3. Это позволило бы им обнаружить, когда ресурс был разрушен в ретроспективе без увеличения срока его службы, и, возможно, мы могли бы гарантировать крах в этих случаях, а некоторые могли бы даже грациозно справиться с этим в ретроспективе. Проблема для меня в том, что слабые ссылки конвертируются в сильные ссылки и наоборот, поэтому среди разработчиков внутри и сторонних разработчиков там кто-то все еще небрежно заканчивает тем, что хранит сильную ссылку в Thing2, хотя слабая ссылка была бы гораздо более уместным.

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

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

Явное уничтожение

Таким образом, я предпочитаю явное уничтожение постоянных ресурсов приложения, например:

on_removal_event:
    // This is ideal to me, not trying to release a bunch of strong
    // references and hoping things get implicitly destroyed.
    destroy(app_resource);

... поскольку мы можем рассчитывать на его освобождение ресурса. Мы не можем быть полностью уверены в том, что что-то там в системе не будет иметь свисающий указатель или слабую ссылку, но по крайней мере эти проблемы, как правило, легко обнаружить и воспроизвести при тестировании. Они не остаются незамеченными на века и накапливаются.

Один сложный случай всегда был многопоточным для меня. В тех случаях, что я нашел полезным, а не полномасштабной сборкой мусора или, скажем, shared_ptr, заключается в том, чтобы просто как-то отложить разрушение:

on_removal_event:
    // *May* be deferred until threads are finished processing the resource.
    destroy(app_resource);

В некоторых системах, где постоянные потоки унифицированы таким образом, что они имеют событие processing, например, мы можем пометить ресурс, который должен быть уничтожен отложенным образом в срезе времени, когда потоки не обрабатываются ( почти начинает ощущаться как GC, но мы сохраняем явное уничтожение). В других случаях мы можем использовать, скажем, подсчет ссылок, но таким образом, чтобы избежать shared_ptr, где счет ссылки ресурсов начинается с нуля и будет уничтожен с использованием этого явного синтаксиса выше, если поток не будет локально продлевать время жизни, увеличивая счетчик временно (например: использование ресурса с ограниченным доступом в локальной функции потока).

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

RAII

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

Поэтому, если бы этот выбор был выбран, и я должен был выбрать один, он легко RAII для меня. Я работаю в домене, где эти утечки без памяти, вызванные совместным владением, абсолютно убийцы, и обвисшая ошибка указателя на самом деле предпочтительнее, если (и, скорее всего, будет) быть пойманной во время тестирования. Даже в каком-то действительно неясном случае, которое он обнаружил поздно, если он проявляет себя в крушении рядом с местом, где произошла ошибка, что еще предпочтительнее, чем использование инструментов профилирования памяти и пытается выяснить, кто забыл выпустить ссылку, пропуская через миллионы строк кода. В моем самом суровом мнении GC представляет больше проблем, чем решает для моего конкретного домена (VFX, который несколько похож на игры с точки зрения организации сцены и состояния приложения), и одна из причин, кроме тех, это может дать разработчикам ложное впечатление, что им не нужно думать об управлении ресурсами и владении постоянными ресурсами приложений, нечаянно вызывая логические утечки влево и вправо.

"Когда происходит сбой RAII"

Единственный случай, который я когда-либо встречал на протяжении всей моей карьеры, когда я не мог придумать какой-либо возможный способ избежать совместного владения каким-либо видом, - это когда я реализовал библиотеку постоянных структур данных, например:

введите описание изображения здесь

Я использовал его для реализации неизменяемой структуры данных сетки, которая может модифицировать порции, не будучи уникальной, например, (тест с 4 миллионами четырехугольников):

введите описание изображения здесь

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

В этом случае вся концепция неизменной структуры данных вращается вокруг совместного владения, чтобы избежать дублирования данных, которые не были уникальными. Это настоящий случай, когда мы не можем избежать совместного владения независимо от того, что (по крайней мере, я не могу думать о каком-либо возможном способе).

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