Не могли бы вы, разработчики C ++, дать нам хорошее описание того, что такое RAII, почему он важен и может ли он иметь какое-либо отношение к другим языкам?
Я сделать немного знаю. Я считаю, что это означает «Приобретение ресурсов - это инициализация». Однако это имя не согласуется с моим (возможно, неправильным) пониманием того, что такое RAII: у меня создается впечатление, что RAII - это способ инициализации объектов в стеке, так что, когда эти переменные выходят за пределы области видимости, деструкторы автоматически вызывается, вызывая очистку ресурсов.
Так почему же это не называется «использование стека для запуска очистки» (UTSTTC :)? Как оттуда добраться до «РАИИ»?
И как вы можете сделать что-то в стеке, что приведет к очистке того, что живет в куче? Кроме того, есть случаи, когда вы не можете использовать RAII? Вы когда-нибудь мечтали о вывозе мусора? По крайней мере, сборщик мусора, который вы могли бы использовать для одних объектов, позволяя управлять другими?
Спасибо.
источник
:(
Тогда я читал всю цепочку писем и даже не считал себя новичком в C ++!Ответы:
RAII говорит вам, что делать: получить свой ресурс в конструкторе! Я бы добавил: один ресурс, один конструктор. UTSTTC - лишь одно из применений этого, RAII - намного больше.
Управление ресурсами - отстой. Здесь ресурс - это все, что требует очистки после использования. Исследования проектов на многих платформах показывают, что большинство ошибок связано с управлением ресурсами - и это особенно плохо в Windows (из-за множества типов объектов и распределителей).
В C ++ управление ресурсами особенно сложно из-за комбинации исключений и шаблонов (стиль C ++). Чтобы заглянуть под капот, см. GOTW8 ).
C ++ гарантирует, что деструктор вызывается тогда и только тогда, когда конструктор завершился успешно. Опираясь на это, RAII может решить множество неприятных проблем, о которых средний программист может даже не подозревать. Вот несколько примеров помимо «мои локальные переменные будут уничтожены, когда я вернусь».
Начнем с чрезмерно упрощенного
FileHandle
класса, использующего RAII:Если построение не удается (за исключением), никакая другая функция-член - даже деструктор - не вызывается.
RAII избегает использования объектов в недопустимом состоянии. это облегчает жизнь еще до того, как мы используем объект.
Теперь давайте посмотрим на временные объекты:
Есть три случая ошибок, которые необходимо обработать: ни один файл нельзя открыть, можно открыть только один файл, можно открыть оба файла, но скопировать файлы не удалось. В реализации без RAII
Foo
пришлось бы обрабатывать все три случая явно.RAII освобождает ресурсы, которые были получены, даже если в одном операторе было получено несколько ресурсов.
Теперь давайте объединим несколько объектов:
Конструктор
Logger
завершится ошибкой, еслиoriginal
конструктор не работает (потому чтоfilename1
не может быть открыт),duplex
конструктор не работает (потому чтоfilename2
не может быть открыт) или запись в файлы внутриLogger
тела конструктора не выполняется. В любом из этих случаевLogger
деструктор не будет вызван, поэтому мы не можем полагаться наLogger
деструктор для освобождения файлов. Но еслиoriginal
был построен, его деструктор будет вызываться во время очисткиLogger
конструктора.RAII упрощает очистку после частичного строительства.
Отрицательные моменты:
Отрицательные моменты? Все проблемы решаются с помощью RAII и умных указателей ;-)
RAII иногда бывает громоздким, когда вам нужно отложенное получение, помещая агрегированные объекты в кучу.
Представьте, что регистратору нужен файл
SetTargetFile(const char* target)
. В этом случае дескриптор, который по-прежнему должен быть членомLogger
, должен находиться в куче (например, в интеллектуальном указателе, чтобы должным образом инициировать уничтожение дескриптора).На самом деле, я никогда не хотел вывоза мусора. Когда я пишу на C #, я иногда чувствую момент блаженства, что мне просто не нужно заботиться, но гораздо больше я скучаю по всем крутым игрушкам, которые можно создать посредством детерминированного разрушения. (использование
IDisposable
просто не режет.)У меня была одна особенно сложная структура, которая могла бы выиграть от GC, где «простые» интеллектуальные указатели будут вызывать циклические ссылки на несколько классов. Мы запутались, тщательно сбалансировав сильные и слабые стороны, но каждый раз, когда мы хотим что-то изменить, мы должны изучить большую диаграмму отношений. GC мог бы быть лучше, но некоторые из компонентов содержали ресурсы, которые следует выпустить как можно скорее.
Примечание к образцу FileHandle: он не был задуман как полный, это всего лишь образец, но оказался неверным. Спасибо Йоханнесу Шаубу за указание и FredOverflow за превращение его в правильное решение C ++ 0x. Со временем я остановился на описанном здесь подходе .
источник
Foo
владеетBar
иBoz
изменяет его, ...Есть отличные ответы, поэтому я просто добавляю кое-что забытое.
0. RAII касается областей применения
RAII касается обоих:
Другие уже ответили об этом, поэтому я не буду вдаваться в подробности.
1. При кодировании на Java или C # вы уже используете RAII ...
Как и мсье Журден с прозой, C # и даже Java люди уже используют RAII, но скрытыми способами. Например, следующий код Java (который на C # написан таким же образом, заменяясь
synchronized
наlock
):... уже использует RAII: получение мьютекса выполняется с помощью ключевого слова (
synchronized
илиlock
), а отмена получения будет выполнена при выходе из области видимости.Это настолько естественное обозначение, что почти не требует объяснений даже для людей, которые никогда не слышали о RAII.
Преимущество C ++ над Java и C # в том, что с помощью RAII можно сделать все, что угодно. Например, нет прямого встроенного эквивалента
synchronized
илиlock
в C ++, но мы все еще можем их иметь.В C ++ это было бы написано:
который можно легко написать на языке Java / C # (с использованием макросов C ++):
2. RAII имеет альтернативное использование
Вы знаете, когда будет вызван конструктор (при объявлении объекта), и вы знаете, когда будет вызван его соответствующий деструктор (на выходе из области видимости), поэтому вы можете написать почти волшебный код, используя только строку. Добро пожаловать в страну чудес C ++ (по крайней мере, с точки зрения разработчика C ++).
Например, вы можете написать объект счетчика (я позволил это в качестве упражнения) и использовать его, просто объявив его переменную, как был использован объект блокировки выше:
что, конечно же, может быть написано, опять же, способом Java / C # с использованием макроса:
3. Почему не хватает C ++
finally
?Предложение
finally
используется в C # / Java для обработки удаления ресурсов в случае выхода из области действия (либо посредствомreturn
исключения, либо сгенерированного исключения).Проницательные читатели спецификации заметят, что в C ++ нет пункта finally. И это не ошибка, потому что C ++ в этом не нуждается, поскольку RAII уже обрабатывает удаление ресурсов. (И поверьте мне, написать деструктор C ++ намного проще, чем написать правильное предложение finally в Java или даже правильный метод Dispose в C #).
Тем не менее, иногда
finally
оговорка была бы крутой. Можем ли мы сделать это на C ++? Да мы можем! И снова с альтернативным использованием RAII.Вывод: RAII - это больше, чем философия в C ++: это C ++
Когда вы достигнете определенного уровня опыта в C ++, вы начнете мыслить в терминах RAII , в терминах автоматического выполнения конструкторов и деструкторов .
Вы начинаете думать в терминах областей , а также
{
и}
персонажи становятся одними из самых важных в вашем коде.И почти все подходит с точки зрения RAII: безопасность исключений, мьютексы, соединения с базой данных, запросы к базе данных, соединение с сервером, часы, дескрипторы ОС и т. Д., И, наконец, что не менее важно, память.
Часть базы данных не является незначительной, так как, если вы согласитесь заплатить цену, вы даже можете писать в стиле « транзакционного программирования », выполняя строки и строки кода, пока в конце концов не решите, хотите ли вы зафиксировать все изменения или, если это невозможно, возврат всех изменений (при условии, что каждая строка удовлетворяет хотя бы Строгой гарантии исключения). (см. вторую часть статьи Herb's Sutter о транзакционном программировании).
И как в пазле, все подходит.
RAII - это неотъемлемая часть C ++, без него C ++ не мог бы быть C ++.
Это объясняет, почему опытные разработчики C ++ так влюблены в RAII и почему RAII - это первое, что они ищут, пробуя другой язык.
И это объясняет, почему сборщик мусора, хотя сам по себе является великолепной технологией, не так впечатляет с точки зрения разработчика C ++:
источник
Посмотри пожалуйста:
Программисты других языков, помимо C ++, используют, знают или понимают RAII?
RAII и интеллектуальные указатели в C ++
Поддерживает ли C ++ блоки «finally»? (И что это за RAII, о котором я все время слышу?)
RAII против исключений
и т.д..
источник
RAII использует семантику деструкторов C ++ для управления ресурсами. Например, рассмотрим умный указатель. У вас есть параметризованный конструктор указателя, который инициализирует этот указатель адресом объекта. Вы размещаете указатель в стеке:
Когда интеллектуальный указатель выходит за пределы области видимости, деструктор класса указателя удаляет связанный объект. Указатель выделяется в стеке, а объект - в куче.
Есть определенные случаи, когда RAII не помогает. Например, если вы используете интеллектуальные указатели с подсчетом ссылок (такие как boost :: shared_ptr) и создаете графоподобную структуру с циклом, вы рискуете столкнуться с утечкой памяти, потому что объекты в цикле будут препятствовать освобождению друг друга. Сборка мусора поможет против этого.
источник
Я хотел бы выразить это немного сильнее, чем предыдущие ответы.
RAII ( Resource Acquisition Is Initialization) означает, что все полученные ресурсы должны быть получены в контексте инициализации объекта. Это запрещает приобретение «голого» ресурса. Причина в том, что очистка в C ++ работает на основе объектов, а не вызовов функций. Следовательно, вся очистка должна выполняться объектами, а не вызовами функций. В этом смысле C ++ более объектно-ориентирован, чем, например, Java. Очистка Java основана на вызовах функций в
finally
предложениях.источник
Я согласен с cpitis. Но хочу добавить, что ресурсами может быть что угодно, а не только память. Ресурс может быть файлом, критическим разделом, потоком или подключением к базе данных.
Это называется «Приобретение ресурсов - это инициализация», потому что ресурс приобретается, когда создается объект, управляющий ресурсом. Если конструктор не работает (то есть из-за исключения), ресурс не приобретается. Затем, когда объект выходит за пределы области видимости, ресурс освобождается. c ++ гарантирует, что все объекты в стеке, которые были успешно созданы, будут уничтожены (включая конструкторы базовых классов и членов, даже если конструктор суперкласса не работает).
Смысл RAII - сделать исключение получения ресурса безопасным. Все полученные ресурсы правильно высвобождаются независимо от того, где возникает исключение. Однако это действительно зависит от качества класса, который получает ресурс (это должно быть безопасным для исключений, а это сложно).
источник
Проблема со сборкой мусора заключается в том, что вы теряете детерминированное разрушение, которое имеет решающее значение для RAII. Когда переменная выходит за пределы области видимости, сборщик мусора решает, когда объект будет удален. Ресурс, удерживаемый объектом, будет удерживаться до тех пор, пока не будет вызван деструктор.
источник
RAII исходит из инициализации выделения ресурсов. По сути, это означает, что когда конструктор завершает выполнение, созданный объект полностью инициализируется и готов к использованию. Это также подразумевает, что деструктор освободит любые ресурсы (например, память, ресурсы ОС), принадлежащие объекту.
По сравнению с языками / технологиями со сборкой мусора (например, Java, .NET), C ++ позволяет полностью контролировать жизнь объекта. Для объекта, выделенного стеком, вы будете знать, когда будет вызван деструктор объекта (когда выполнение выходит за пределы области видимости), что на самом деле не контролируется в случае сборки мусора. Даже используя интеллектуальные указатели в C ++ (например, boost :: shared_ptr), вы будете знать, что при отсутствии ссылки на указанный объект будет вызываться деструктор этого объекта.
источник
Когда появляется экземпляр int_buffer, он должен иметь размер, и он будет выделять необходимую память. Когда он выходит за пределы области видимости, вызывается деструктор. Это очень полезно для таких вещей, как объекты синхронизации. Рассматривать
Нет, не совсем.
Никогда. Сборка мусора решает только очень небольшую часть динамического управления ресурсами.
источник
Здесь уже есть много хороших ответов, но я просто хотел бы добавить:
Простое объяснение RAII заключается в том, что в C ++ объект, размещенный в стеке, уничтожается всякий раз, когда он выходит за пределы области видимости. Это означает, что будет вызван деструктор объектов, который сможет выполнить всю необходимую очистку.
Это означает, что если объект создается без «нового», «удалять» не требуется. И это также идея «умных указателей» - они находятся в стеке и по сути являются оболочкой для объекта на основе кучи.
источник
RAII - это аббревиатура от Resource Acquisition Is Initialization.
Этот метод очень уникален для C ++ из-за их поддержки как для конструкторов, так и для деструкторов и почти автоматически конструкторов, которые соответствуют этим передаваемым аргументам, или, в худшем случае, конструктор по умолчанию вызывается и деструкторы, если предоставленная явная информация вызывается в противном случае который добавлен компилятором C ++, вызывается, если вы явно не написали деструктор для класса C ++. Это происходит только для объектов C ++, которые управляются автоматически, т. Е. Не используют свободное хранилище (память выделяется / освобождается с помощью операторов new, new [] / delete, delete [] C ++).
Техника RAII использует эту функцию автоматически управляемого объекта для обработки объектов, которые создаются в куче / свободном хранилище, путем явного запроса дополнительной памяти с помощью new / new [], который должен быть явно уничтожен вызовом delete / delete [] . Класс автоматически управляемого объекта обернет этот другой объект, созданный в куче / свободной памяти. Следовательно, когда запускается конструктор автоматически управляемого объекта, обернутый объект создается в куче / свободной памяти, и когда дескриптор автоматически управляемого объекта выходит за пределы области видимости, автоматически вызывается деструктор этого автоматически управляемого объекта, в котором обернутый объект объект уничтожается с помощью удаления. С концепциями ООП, если вы обернете такие объекты внутри другого класса в частной области видимости, у вас не будет доступа к членам и методам обернутых классов и Это причина, по которой разработаны интеллектуальные указатели (также известные как классы дескрипторов). Эти интеллектуальные указатели предоставляют обернутый объект как типизированный объект для внешнего мира и там, позволяя вызывать любые члены / методы, из которых состоит открытый объект памяти. Обратите внимание, что интеллектуальные указатели имеют разные разновидности, основанные на разных потребностях. Вы должны обратиться к Современному программированию на C ++ Андрея Александреску или к реализации / документации библиотеки boost (www.boostorg) shared_ptr.hpp, чтобы узнать больше об этом. Надеюсь, это поможет вам понять RAII. Вы должны обратиться к Современному программированию на C ++ Андрея Александреску или к реализации / документации библиотеки boost (www.boostorg) shared_ptr.hpp, чтобы узнать больше об этом. Надеюсь, это поможет вам понять RAII. Вы должны обратиться к Современному программированию на C ++ Андрея Александреску или к реализации / документации библиотеки boost (www.boostorg) shared_ptr.hpp, чтобы узнать больше об этом. Надеюсь, это поможет вам понять RAII.
источник