Является ли совместное владение объектами признаком плохого дизайна? - PullRequest
11 голосов
/ 25 декабря 2009

Фон : При чтении Др. В работах Страуструпа и часто задаваемых вопросах я замечаю некоторые сильные «мнения» и отличные советы от легендарного учёного и программиста по CS. Один из них около shared_ptr в C ++ 0x. Он начинает объяснять о shared_ptr и о том, как оно представляет совместное владение указанным объектом. В последней строке он говорит, а я цитирую :

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

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

Ответы [ 4 ]

14 голосов
/ 25 декабря 2009

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

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

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

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

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

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

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

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

8 голосов
/ 25 декабря 2009

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

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

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

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

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

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


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

4 голосов
/ 04 февраля 2018

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

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

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

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

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

enter image description here

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

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

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

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

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

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

enter image description here

И именно из-за этой безмолвной природы я склонен не любить совместную собственность со страстью, и 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 терпит неудачу»

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

enter image description here

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

enter image description here

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

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

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

3 голосов
/ 26 декабря 2009

Сборка мусора - это шаблон дизайна? Я не знаю.

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

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

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

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

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

...