Raii c что это
Перейти к содержимому

Raii c что это

Идиома RAII в программировании на С++

Идиома RAII в программировании на С++

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

Очень легко забыть освободить ресурс, что может привести к сбоям, к зависанию программы и другим неприятным и сложным в отладке эффектам. Идиома программного дизайна RAII (англ. Resource Acquisition Is Initialization ("получение ресурса есть инициализация")) позволяет автоматизировать освобождение ресурсов, освободив программиста от лишнего беспокойства.

В ходе этого вебинара будет рассмотрена концепция RAII применительно к языку C++, от идеи до разработки собственных RAII-классов. Также мы рассмотрим различные RAII-классы из стандартной библиотеки С++11.

Автор и ведущий вебинара — Ломакин Вячеслав
Специалист по языкам С и C++.
Работал в корпорации МИГ программистом ПО для лётных тренажёров на языке С++.
Автор и участник различных коммерческих проектов, от экспертных систем до ПО для интернет-коммерции и игровых программ.

Разработка легче при правильном подходе — профессия «Разработчик Microsoft».

RAII и интеллектуальные указатели на C++

на практике с C++, что такое RAII, что смарт-указатели, как они реализованы в программе и каковы преимущества использования RAII с интеллектуальными указателями?

6 ответов

простой (и, возможно, чрезмерно используемый) пример RAII-это класс файлов. Без RAII код может выглядеть примерно так:

другими словами, мы должны убедиться, что мы закрываем файл, как только мы закончим с ним. Это имеет два недостатка — во-первых, везде, где мы используем файл, нам придется вызывать File::close () — если мы забудем это сделать, мы держим файл дольше, чем нам нужно. Вторая проблема заключается в том, что, если исключение создается до закрытия файл?

Java решает вторую проблему, используя предложение finally:

C++ решает обе проблемы с помощью RAII — то есть закрывает файл в деструкторе файла. Пока объект File уничтожается в нужное время (что должно быть в любом случае), закрытие файла позаботится о нас. Итак, наш код теперь выглядит примерно так:

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

на смарт-указатели-много времени, мы просто создаем объекты в стеке. Например (и кража примера из другого ответа):

это отлично работает-но что, если мы хотим вернуть str? Мы могли бы написать так:—14—>

Итак, что в этом плохого? Ну, возвращаемый тип-std:: string — значит, мы возвращаемся по значению. Это означает, что мы копируем ул. и фактически вернуть копию. Это может быть дорого, и мы могли бы избежать затрат на копирование. Поэтому мы могли бы придумать идею возврата по ссылке или по указателю.

к сожалению, этот код не работает. Мы возвращаем указатель на str-но str был создан в стеке, поэтому мы будем удалены после выхода из foo (). Другими словами, к тому времени, когда вызывающий получает указатель, он бесполезен (и, возможно, хуже, чем бесполезен, поскольку его использование может вызвать все виды фанки ошибок)

Итак, каково решение? Мы могли бы создать str в куче, используя new-таким образом, когда foo() будет завершен, str не будет уничтожен.

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

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

теперь shared_ptr будет подсчитывать количество ссылок на str. Для пример

теперь есть две ссылки на одну и ту же строку. Как только не останется ссылок на str, он будет удален. Таким образом, вам больше не нужно беспокоиться об удалении его самостоятельно.

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

Итак, давайте попробуем другой пример, используя наш класс File.

предположим, мы хотим использовать файл в качестве журнала. Это означает, что мы хотим открыть наш файл в режиме добавления только:

теперь давайте установим наш файл в качестве журнала для нескольких других объектов:

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

но тогда кто несет ответственность за удаление файла? Если ни удалить файл, то у нас есть как память, так и утечка ресурсов. Мы не знаем, закончит ли foo или bar файл первым, поэтому мы не можем ожидать, что они сами удалят файл. Например, если foo удаляет файл до того, как bar закончив с этим, bar теперь имеет недопустимый указатель.

Итак, как вы уже догадались, мы могли бы использовать умные указатели, чтобы помочь нам.

теперь никому не нужно беспокоиться об удалении файла — после того, как foo и bar закончили и больше не имеют ссылок на файл (возможно, из-за foo и bar уничтожается), файл будет автоматически удален.

RAII это странное название для простой, но удивительной концепции. Лучше имя Scope Bound Resource Management (SBRM). Идея в том, что часто вам случается выделять ресурсы в начале блока, и нужно освободить его при выходе из блока. Выход из блока может произойти при нормальном управлении потоком, выпрыгивании из него и даже при исключении. Чтобы охватить все эти случаи, код становится более сложным и избыточным.

просто пример выполнения без SBRM:

как вы видите, есть много способов получить pwned. Идея заключается в том, что мы инкапсулируем управление ресурсами в класс. Инициализация его объекта приобретает ресурс («приобретение ресурса-это инициализация»). В момент выхода из блока (область блока) ресурс снова освобождается.

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

обычно умные указатели-это тонкие обертки вокруг new / delete, которые просто вызывают delete когда ресурс, которым они владеют, выходит за рамки. Некоторые умные указатели, такие как shared_ptr, позволяют вам сообщить им так называемый deleter, который используется вместо delete . Это позволяет вам, например, управлять дескрипторами окон, ресурсами регулярных выражений и другими произвольными вещами, пока вы говорите shared_ptr о правильном делетере.

существуют различные смарт-указатели для различных целей:

unique_ptr не

в отличие от auto_ptr, unique_ptr можно поместить в контейнер, потому что контейнеры смогут содержать не копируемые (но подвижные) типы, такие как streams и unique_ptr.

scoped_ptr

shared_ptr

как вы видите, plot-source (функция fx) является общим, но каждый из них имеет отдельную запись, на которой мы устанавливаем цвет. Существует класс weak_ptr, который используется, когда код должен ссылаться на ресурс, принадлежащий смарт-указателю, но не должен владеть ресурсом. Вместо передачи необработанного указателя следует создать weak_ptr. Он выдаст исключение, когда заметит, что вы пытаетесь получить доступ к ресурсу по пути доступа weak_ptr, даже если shared_ptr больше не владеет ресурсом.

предпосылки и причины просты, в концепции.

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

C++ не требует RAII, но все чаще признается, что использование методов RAII приведет к более надежному коду.

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

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

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

Smart pointer-это вариация RAII. RAII означает, что приобретение ресурсов является инициализацией. Smart pointer получает ресурс (память) перед использованием, а затем автоматически выбрасывает его в деструктор. Происходят две вещи:—1—>

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

например, Другим примером является сетевой сокет RAII. В этом случае:

  1. открываем сетевой сокет прежде чем мы используем его, всегда, даже когда мы не чувствуем, как . трудно сделать это по-другому с RAII. Если вы попытаетесь сделать это без RAII, вы можете открыть пустой сокет для, скажем, MSN-соединения. Затем сообщение типа » давай сделаем это сегодня вечером» возможно, вас не переведут, пользователи не будут трахаться, и вы можете рискнуть быть уволены.
  2. закрыть сетевой сокет даже когда есть ошибки. Ни один сокет не остается висящим, поскольку это может помешать ответному сообщению «уверен, что я буду внизу»от удара отправителя.

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

источники c++ интеллектуальных указателей в миллионах по всей сети, включая ответы выше меня.

Boost имеет ряд из них, включая те, в импульс.Interprocess для общей памяти. Это значительно упрощает управление памятью, особенно в ситуациях, вызывающих головную боль, например, когда у вас есть 5 процессов, разделяющих одну и ту же структуру данных: когда все закончат с куском памяти, вы хотите, чтобы он автоматически освободился и не должен сидеть там, пытаясь выяснить, кто должен отвечать за вызов delete на куске памяти, чтобы вы не закончили утечкой памяти, или указатель, который ошибочно освобождается дважды и может повредить всю кучу.

независимо от того, что происходит, bar будет правильно удален, как только область функции foo() будет оставлена позади.

внутренне реализации std::string часто используют указатели подсчета ссылок. Таким образом, внутренняя строка должна быть скопирована только при изменении одной из копий строк. Поэтому интеллектуальный указатель с подсчетом ссылок позволяет копировать что-либо только при необходимости.

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

Беспощадное программирование

Resource Acquisition Is Initialization – «Завладение ресурсом есть его инициализация». Если вдуматься в смысл, то чем-то напоминает ленивую инициализацию.
Однако для нас, для C++ программистов, наиболее важным является следствие из этой идиомы: «Освобождение ресурса есть его деинициализация».

К примеру, нужно скопировать содержимое одного файла в другой:

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

Давайте подумаем над недостатками такого кода. Вносить изменения в него крайне опасно: что-нибудь пропустишь – и ресурс (память или файл) останется неосвобожденным. Кроме того, если по каким-либо причинам исключение произойдет при работе с файлом через fread или fwrite, то все ресурсы останутся жить в зомбиленде. Кроме того, присутствует дублирование кода, читабельность стремиться к нулю со всеми вытекающими.

И тут приходят на помощь особенности языка C++. Давайте вспомним локальные переменные и области видимости.

Что произойдет с переменными i и myClass по достижению конца области видимости? Переменная i будет вытолкнута из стека, а для myClass будет вызван деструктор класса. А ведь это уже интересно.

Когда в этом случае будет вызван деструктор myClass? Правильно, в каждом из случаев выхода из области видимости: если i=1, то перед return, если i=2 – перед генерацией исключения. При всех остальных значениях i – естественным путем, так как достигнута закрывающая скобка.
Это напоминает первый пример. Если написать откаты в деструкторах объектов, то не нужно их специально вызывать: они будут вызваны автоматически, когда это понадобится. И не останется зомби-файлов и утечек памяти, даже случайно. Ну, и по аналогии, в конструкторе нужно инициализировать объекты. Это и есть RAII.
Пишем код, лишенный всех недостатков. Сначала определяем классы, которые будут служить RAII-врапперами:

И, собственно, рефакторинг участка кода:

Вот и все! Память больше освобождать не нужно: она освободится автоматически, когда переменная buffer станет недоступна. И с закрытиями файлов также: все будет закрыто при выходе из области видимости переменных file1 и file2.
Стоит ли использовать идиому RAII? Поверьте, стоит. Даже если у вас феноменальная память и bounds checker в голове, рано или поздно вы допустите ошибку. Так почему бы не использовать RAII, хуже то от этого не становится.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *