Поддерживает ли C ++ блоки «finally»? (И что это за RAII, о котором я все время слышу?)

Поддерживает ли C ++ «наконец 'блоки?

Что такое идиома RAII?

В чем разница между идиомой RAII C ++ и оператором C # 'using' ?


person Kevin    schedule 02.10.2008    source источник
comment
Разрушение - это отказ от ресурсов - DIRR ... Нет, у меня не работает. = P   -  person 18446744073709551615    schedule 16.11.2015


Ответы (16)


Нет, C ++ не поддерживает блоки finally. Причина в том, что C ++ вместо этого поддерживает RAII: «Получение ресурсов - это инициализация» - плохое название для действительно полезной концепции.

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

Обычно RAII используется для блокировки мьютекса:

// A class with implements RAII
class lock
{
    mutex &m_;

public:
    lock(mutex &m)
      : m_(m)
    {
        m.acquire();
    }
    ~lock()
    {
        m_.release();
    }
};

// A class which uses 'mutex' and 'lock' objects
class foo
{
    mutex mutex_; // mutex for locking 'foo' object
public:
    void bar()
    {
        lock scopeLock(mutex_); // lock object.

        foobar(); // an operation which may throw an exception

        // scopeLock will be destructed even if an exception
        // occurs, which will release the mutex and allow
        // other functions to lock the object and run.
    }
};

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

Для тех, кто знаком с C # или VB.NET, вы можете узнать, что RAII похож на Детерминированное уничтожение .NET с помощью операторов IDisposable и 'using'. Действительно, эти два метода очень похожи. Основное отличие состоит в том, что RAII детерминированно высвобождает ресурсы любого типа, включая память. При реализации IDisposable в .NET (даже в языке .NET C ++ / CLI) ресурсы будут выделены детерминированно, за исключением памяти. В .NET память не выделяется детерминированно; память освобождается только во время циклов сборки мусора.

† Некоторые люди считают, что «Разрушение - это отказ от ресурсов» - более точное название идиомы RAII.

В C ++ оператор finally НЕ требуется из-за RAII.

person Kevin    schedule 02.10.2008
comment
RAII застрял - его действительно нельзя изменить. Было бы глупо пытаться это сделать. Однако вы должны признать, что Resource Acquisition Is Initialization - все еще довольно плохое название. - person Erik Forbes; 02.10.2008
comment
Действительно плохое имя, я все еще не могу понять его. - person Kevin; 02.10.2008
comment
Я согласен с названием - жаль, что мы застряли с ним. - person user15071; 02.10.2008
comment
SBRM == Управление ресурсами с ограничением объема - person Michael Burr; 02.10.2008
comment
Любой, кто обладает навыками разработки не только программного обеспечения в целом, не говоря уже об улучшенных методах, не может дать достойного оправдания такой ужасающей аббревиатуре. - person Johannes Schaub - litb; 25.11.2008
comment
@Kevin: Во время использования RAII действительно не похож на _1_: вы не можете объединить _2_-оператор над инициализатором / финализатором вашего класса. Вы должны быть откровенны и не забывать _3_-утверждение. Вложение становится ужасным. Варианты использования _4_ по сравнению с RAII в основном равны нулю: блокировки с ограниченным объемом, векторы, строки, общие указатели, транзакции и все это без необходимости вручную писать _5_. В том виде, в каком вы это представляете, _6_ - плохая защита перед RAII, и я понимаю, что многие разработчики .net изо всех сил пытаются убедить себя, что RAII похож, но в глубине души они (должны) знать, что это не так. - person Hardryv; 10.04.2012
comment
Это заставляет вас застрять, когда вам нужно что-то очистить, что не соответствует времени жизни любого объекта C ++. Я предполагаю, что у вас получится Lifetime Equals C ++ Class Liftime Or Else It Gets Ugly (LECCLEOEIGU?). - person Sebastian Mach; 06.07.2012
comment
Я думаю о чем-то похожем на Уоррена: подождите, что произойдет, если у вас есть код, который должен обязательно выполняться, но не в конце жизненного цикла объекта? Разве для finally все еще не существует варианта использования, и поэтому его отсутствие в C ++ является дырой в пространстве дизайна? Кроме того, использование деструктора означает, что C ++ всегда должен реализовываться с использованием ДЕТЕРМИНИСТИЧЕСКОГО времени жизни памяти. Ни одна из будущих версий C ++ не сможет использовать вместо этого сборку мусора, потому что все полагаются на своевременное выполнение деструкторов. - person Warren P; 27.12.2012
comment
@WarrenP Тогда вы должны обернуть это что-то в класс. В этом вся суть RAII, и на самом деле он часто заставляет вас делать лучший дизайн. - person ToolmakerSteve; 21.11.2013
comment
@ToolMakerSteve То же самое и с вами, заверните. Это также не дыра в дизайне, поскольку, если вы хотите сделать gc на C ++, вы, вероятно, сделаете это на необработанных указателях (или, может быть, на конкретном классе указателя, но тогда это еще менее актуально), а удаление всегда происходит перед gc может вмешаться. Другими словами, это повлияет только на сломанный (/ утечку) текущий код, в котором вы уже не могли полагаться на конструкторы. - person Jasper; 21.03.2014
comment
@Jasper: Вот почему в каждой кодовой базе так много почти совершенно бесполезных классов. Потому что C ++ не оставляет вам другого выбора. Это называется языковой бородавкой. - person Jasper; 21.03.2014
comment
@WarrenP Нет, это называется принцип единственной ответственности. - person Warren P; 21.03.2014
comment
Мне всегда нравится, что единственная ответственность класса состоит в том, что у него больше пропускной способности, чем бумажные ограничения этого языка. - person Jasper; 21.03.2014
comment
Что ж, лично я предпочитаю, когда язык решает не творить магию, а вместо этого дает вам мощные инструменты, чтобы делать что-то самостоятельно, и сообщая, что единственный способ сделать это - поддерживаемый и масштабируемый способ, который решает проблему внутри кода. честно говоря, для меня не проблема. Посмотрите на альтернативы, Java передает это в руки вызывающего кода с _1_, C # дает тот же метод и некоторую магию с помощью _2_, языки сценариев часто дают вам аналогичные решения, но также говорят, что ограниченное время выполнения должно предотвратить большие проблемы. Я бы взял RAII в любой день. - person Warren P; 21.03.2014
comment
@Jasper: Обертывание в класс, очевидно, имеет смысл, когда (управляемое состояние) является частью того, что делает служба. Однако это кажется обременительным, когда это единовременная потребность (клиента или кода графического интерфейса пользователя) как часть ответа на конкретное действие пользователя. Например, в какой-то момент моей клиентской логики мне нужно запомнить текущее значение нескольких (полей механизма рендеринга - я не могу изменить его исходный код), установить для них необходимые значения, а затем восстановить их, когда это будет сделано. Создание класса для этого единственного использования в единственной программе - это излишне. Наконец-то было бы краткое и безопасное решение. - person Jasper; 21.03.2014
comment
@Jasper: Я удалил один из своих комментариев - решил, что добавление недетерминированного GC в C ++ сделает и без того слишком сложный язык еще более головокружительным. Настоящая проблема в том, что годы, когда я программировал на C ++, до того, как появились Java или .Net, я находил это неприятным - и до сих пор чувствую, когда мне нужно его использовать. Я бы хотел увидеть новое изобретение C ++, начиная с нуля, такое же чистое, как python или C #, с библиотекой классов, основанной на RAII, умных указателях и / или слабых ссылках: детерминированный Решения для ГХ. Да, все, что существует в C ++, но более чистый язык для размышлений. По общему признанию, не по теме ... - person ToolmakerSteve; 22.03.2014
comment
@ToolmakerSteve Перед этим удалил один из моих ответов на него: P, когда я понял, что совершал ошибку. В любом случае, я согласен с тем, что чистый неуправляемый язык был бы замечательным (или, возможно, даже мог бы быть хороший способ смешать управляемый и неуправляемый). Даже Бьярн Соуструп в основном сказал то же самое (теперь он предпочел бы не иметь багажа C-совместимости, но он не может изменить это сейчас). Я обнаружил, что необходимость думать о владении - это хорошо. Конечно, вы почти всегда должны использовать вещи C ++ вместо C-вещей, что не всегда случается и не всегда было возможно в те времена, когда Java еще не существовала. - person ToolmakerSteve; 23.03.2014
comment
Лично я не считаю этот _1_ действительно хорошей альтернативой блоку finally. Это просто означает, что для надлежащей очистки вы должны заключить ресурсы в искусственные объекты, чтобы получить автоматическую очистку, как указал Уоррен П в своем комментарии выше (Language wart). - person Jasper; 23.03.2014
comment
@Devolus Вы можете создать очень простой объект-шаблон, чтобы делать это для любых двух методов, которые вам нужно вызвать при инициализации и деинициализации. Честно говоря, я бы не удивился, если бы такой шаблонный класс уже был в STL. (Но его, скорее всего, нет (пока?)) - person Devolus; 18.07.2014
comment
Да, потому что это намного меньше кода, чем ‹snark› mutex_.lock (); попробуйте {foobar (); } наконец {mutex_, unlock (); } ‹/Snark› объекты-оболочки, шаблоны, мальчик, этот c ++ 11, конечно, намного проще! - person Jan Smrčina; 25.04.2015
comment
@TonyBenBrahim Умножается на всю вашу базу кода, применяется ко всем ресурсам, для которых вам нужна детерминированная очистка? да. Да, это. - person Tony BenBrahim; 23.05.2015
comment
@Cubic, да, теперь я это понимаю. Однако требует некоторого привыкания, исходящего от Java. - person Cubic; 04.06.2015
comment
RAII? Разве это не та организация, которая подала в суд на Napster и кучу других производителей инструментов для обмена файлами? - person Tony BenBrahim; 06.06.2015
comment
@Jasper Я могу придумать ситуацию (возможно, необычную, но я не знаю), в которой, вероятно, было бы чище иметь хотя бы _1_, при условии, что это не было необходимо в нескольких местах: Работа напрямую с оборудованием. Предположим, что определенная область памяти обычно должна иметь флаг полной очистки, но этот флаг должен быть изменен во время определенных операций. Если эти операции выполняются одной функцией, было бы чище иметь _2_, - person Damian Yerrick; 24.09.2015
comment
чем нужно было бы написать класс, чтобы обернуть указатель, не являющийся владельцем, который используется только этой одной функцией: в этом случае класс просто добавит уровень косвенности и сделает менее понятным для программиста, что происходит. Теперь, если бы флаг нужно было проверять в нескольких местах, это была бы другая история, но этот конкретный дизайн был бы немного чище, не разбивая RAII в неудобную кладж. - person Justin Time - Reinstate Monica; 22.12.2016
comment
Однако в целом RAII определенно является более чистой альтернативой, IMO. Мне просто странно, что в языке нет и того, и другого, хотя иногда простой одноразовый может быть чище, чем класс, который используется только в одном конкретном месте для одной конкретной задачи. - person Justin Time - Reinstate Monica; 22.12.2016
comment
Опять же, я из тех людей, которые предпочитают иметь именно тот инструмент для данной работы, а не импровизировать с другим, если это возможно, так что это, вероятно, влияет на мое убеждение в том, что должны быть доступны оба варианта, а не только один или один. разное. - person Justin Time - Reinstate Monica; 22.12.2016
comment
@Jan Этого нет в стандартной библиотеке C ++, но есть BOOST_SCOPE_EXIT, если вы можете простить синтаксический шаблон, и что тело вашего кода не должно бросать. boost.org/doc/libs/ 1_65_1 / libs / scope_exit / doc / html / scope_exit / - person Justin Time - Reinstate Monica; 22.12.2016
comment
нам нужно наконец, точка. - person Max Barraclough; 03.11.2017
comment
Нет, не делаем. Период. - person Marc; 30.11.2017
comment
Немного сложное объяснение после того, как вы написали основную идею разрушения, чтобы освободить память (это было более ясно до того, как я начал читать последнюю часть ответа), но все равно спасибо! - person Fernando Silveira; 19.11.2018
comment
Я согласен с тем, что RAII элегантно решает проблему обеспечения освобождения ресурсов. Это не решает элегантным способом, гарантирующим, что что-то всегда будет выполняться (например, ведение журнала). - person Ivan Silkin; 04.10.2019
comment
_1_ Для деструкторов C ++ также важно не генерировать исключения именно по этой причине. - person Nicolas Bousquet; 06.04.2020

RAII переносит ответственность за безопасность исключений с пользователя объекта на проектировщика (и разработчика) объекта. Я бы сказал, что это правильное место, поскольку тогда вам нужно только один раз получить правильную безопасность исключений (в дизайне / реализации). Используя finally, вам нужно обеспечить правильную безопасность исключений каждый раз, когда вы используете объект.

Также IMO код выглядит аккуратнее (см. Ниже).

Пример:

Объект базы данных. Чтобы убедиться, что соединение с БД используется, оно должно быть открыто и закрыто. Используя RAII, это можно сделать в конструкторе / деструкторе.

Использование RAII значительно упрощает правильное использование объекта БД. Объект DB будет правильно закрываться с помощью деструктора, как бы мы ни пытались злоупотребить им.

Java Like, наконец

void someFunc()
{
    DB    db("DBDesciptionString");
    // Use the db object.

} // db goes out of scope and destructor closes the connection.
  // This happens even in the presence of exceptions.

При использовании, наконец, правильное использование объекта делегируется пользователю объекта. т.е. Ответственность за правильное явное закрытие соединения с БД лежит на пользователе объекта. Теперь вы можете утверждать, что это можно сделать в финализаторе, но ресурсы могут иметь ограниченную доступность или другие ограничения, и поэтому вы обычно хотите контролировать выпуск объекта, а не полагаться на недетерминированное поведение сборщика мусора.

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

void someFunc()
{
    DB      db = new DB("DBDesciptionString");
    try
    {
        // Use the db object.
    }
    finally
    {
        // Can not rely on finaliser.
        // So we must explicitly close the connection.
        try
        {
            db.close();
        }
        catch(Throwable e)
        {
           /* Ignore */
           // Make sure not to throw exception if one is already propagating.
        }
    }
}

Также это простой пример.
Когда у вас есть несколько ресурсов, которые необходимо освободить, код может стать сложным.

Более подробный анализ можно найти здесь: http://accu.org/index.php/journals/236

C ++ как RAII

person Martin York    schedule 02.10.2008
comment
@Cemafor: Причина, по которой C ++ не выбрасывает исключения из деструктора, отличается от Java. В Java это будет работать (вы просто потеряете исходное исключение). В C ++ это действительно плохо. Но суть C ++ в том, что вам нужно сделать это только один раз (разработчиком класса), когда он пишет деструктор. В Java вы должны делать это в момент использования. Таким образом, пользователь класса несет ответственность за то, чтобы в любое время написать одну и ту же шаблонную пластину. - person Cemafor; 24.05.2013
comment
Если это необходимо, вам тоже не нужен RAII. Избавимся от этого! :-) Шутки в сторону, RAII подходит во многих случаях. Что делает RAII более громоздким, так это случаи, когда вы хотите выполнить некоторый код (не связанный с ресурсами), даже если приведенный выше код был возвращен раньше. Для этого вы либо используете gotos, либо разделяете его на два метода. - person Martin York; 24.05.2013
comment
@Trinidad: Вы должны быть более конкретными, чем это. Вам следует задать вопрос о ситуациях, когда RAII является более громоздким, и получить реальную обратную связь о том, как вам следует это делать. - person Trinidad; 09.11.2014
comment
@LokiAstari О, это довольно просто, представьте, например, что я создаю структуру данных из файла XML и хочу распечатать ее в конце функции. Дело в том, что в моем коде есть куча _1_ в загрузочной части, поэтому мне не нужно делать много _2_ в будущем. RAII был бы очень громоздким, так как мне потребовалось бы создать класс, просто чтобы сделать красивую печать, нелепо. Либо вы используете _3_s вместо _4_s, либо отделяете загрузку от печати как разные функции. Я согласен, возможно, это не лучший пример, но нелепо думать, что RAII = наконец - person Martin York; 09.11.2014
comment
@Trinidad: Это не так просто, как вы думаете (поскольку все ваши предложения, кажется, выбирают худшие из возможных вариантов). Вот почему вопрос может быть лучшим местом для изучения этого, чем комментарии. - person Trinidad; 10.11.2014
comment
наконец, java 7+: попробуйте (DB db = new DB (DBDesciptionString)) {// используйте db} - person Martin York; 10.11.2014
comment
@TonyBenBrahim: По крайней мере, Java движется к C ++ небольшими шагами. Но это принятие директивы использования C #. Шаг, но не такой мощный, как RAII ;-) - person Tony BenBrahim; 23.05.2015
comment
Хотя я думаю, что ваш ответ очень хорош, я бы возражал против RAII. Думаю, лучше было бы сказать: нет, но вы можете избежать промаха, используя RAII. - person Martin York; 23.05.2015
comment
Итак, если деструкторы C ++ не могут генерировать исключения и не имеют возвращаемых значений, как, черт возьми, вы должны сообщать об ошибках? Например, если деструктору соединения с базой данных необходимо зафиксировать или откатить незавершенную транзакцию, или если деструктору файлового потока необходимо очистить буфер, и эта операция завершится неудачно по какой-либо причине. Вы можете решить эту проблему, введя явную функцию очистки (закрыть / удалить / отключить / и т. Д.), Которая возвращает код ошибки, но в основном это делает бесполезными неявные вызовы деструктора C ++. - person peterh; 23.11.2016
comment
@ dan04: деструкторы C ++ МОГУТ вызывать исключения. Обычно это не самая лучшая идея. Они просто не могут сгенерировать исключение, когда исключение уже распространяется. См .: stackoverflow.com/ questions / 130117 / и codereview.stackexchange.com/questions/540/ (прочтите последний комментарий к этому). - person dan04; 10.02.2017
comment
@ dan04 Для этих случаев есть другая идиома. Это называется защитой прицела. Страж области видимости принимает лямбда-указатель / указатель на функцию / все, что вы хотите, при построении и выполняет его при уничтожении. Это позволяет вам определить раздел кода, который эффективно работает как блок finally, за исключением того, что код очистки можно легко отключить, изменить для других областей или внедрить в другие места. Я вообще не рекомендую использовать исключения в любом неявно вызываемом коде (деструкторах IE), поскольку это может привести к возникновению исключений в непредвиденное время. - person Martin York; 11.02.2017
comment
Критика не требуется из-за RAII: существует множество случаев, когда добавление специального RAII было бы слишком большим количеством шаблонного кода для добавления, а попытка наконец-то была бы просто чрезвычайно уместной. - person LivePastTheEnd; 26.04.2017
comment
@ceztko Я бы сказал, что try / finally использует гораздо более bolierplate. Проблема в том, что try / finally нужно выполнять каждый раз, когда он используется (см. Пример выше). Хотя RAII должен быть выполнен первоначальным автором только один раз. Другая проблема с try / finally заключается в том, что вы перекладываете бремя правильного использования с автора на пользователя. Заставить пользователя делать это каждый раз правильно намного сложнее, чем заставить автора делать это правильно один раз. - person ceztko; 10.03.2019
comment
@MartinYork Я не говорю, что вместо RAII всегда следует использовать try-finally. Вы правы в том, что API-интерфейсы должны предоставлять соответствующие деструкторы для своих структур или обеспечивать защиту RAII, где это необходимо (например, для обработки транзакций). Я говорю о том, что в конечном пользовательском коде есть некоторые обстоятельства, когда добавление RAII - не самый надежный инструмент, который он, возможно, захочет использовать, и попытка наконец-то выполнит свою работу без создания скучных специальных структур для RAII. Короче говоря: было бы хорошо иметь и то, и другое, и мудрый программист использовал бы наиболее подходящие в каждой ситуации. - person Martin York; 11.03.2019
comment
@MartinYork Иногда различие между пользователем и автором просто не применяется. Скажем, в конце моего _1_ я хочу записать файл с надписью ERROR или OK, в зависимости от того, что произошло. Я бы сделал что-нибудь вроде _2_. Теперь мне нужно написать для этого класс, и я стал автором класса, который мне не понадобился бы, если бы я мог исправить это как пользователь. - person ceztko; 11.03.2019
comment
@PieterNuyts Верно. Но, по-вашему, каждый раз, когда вы захотите использовать этот шаблон, вам придется вручную добавлять все вышеперечисленные шаблоны (и не забудьте сделать это правильно и проверить, что он работает правильно). По-моему, вы делаете это один раз в классе. Теперь вы можете протестировать его один раз (он навсегда останется верным после тестирования), и вы можете повторно использовать шаблон, просто объявив объект в правильной области. Так что, если вы сделаете это только один раз, то, конечно, это может сработать проще. Но как часто вы делаете что-то один раз в жизни? Существуют также библиотеки, которые просто выполняют лямбду при уничтожении. - person PieterNuyts; 28.02.2020
comment
@PieterNuyts _1_ Мне кажется, что он не такой уж болтливый. - person Martin York; 28.02.2020
comment
@MartinYork, На первый взгляд, ваш фрагмент кода примерно такой же длины, как мой (и вы не учли блок _1 _ / _ 2_, который все еще был бы необходим), поэтому я не уверен, почему вы считаете его менее банальным. Но любой, кто хочет понять вашу, должен знать или искать, что такое _3_ и как именно оно работает. - person Martin York; 28.02.2020
comment
@PieterNuyts Все дело в том, что вам не нужен блок try / catch (или, наконец,), поскольку все, что будет сделано, - это соответствующий деструктор. В добавляемом мной примере finally есть простой альтернативный метод обеспечения функциональности finally, если вы действительно этого хотите. Обычно вы этого не делаете, но его просто добавить в C ++. Что касается знания того, что делает try: переход к ближайшему к вам стандарту (C ++ gsl = ›modernescpp.com/index.php/) - person PieterNuyts; 02.03.2020
comment
@PieterNuyts Вот как я это вижу. Более чем счастлив обсудить эту тему, если хотите. - person Martin York; 03.03.2020
comment
Никакой деструктор не может заменить блок catch, поскольку деструкторы не могут перехватывать исключения, созданные извне. Таким образом, вы можете отбросить блок finally, но вам все равно понадобится try / catch (конечно, при условии, что вы хотите перехватить и обработать исключение). - person Martin York; 03.03.2020
comment
Вы правы, что я не учел исключения внутри блока finally. Итак, ваш код, возможно, немного короче. Тем не менее, то, что какая-то функция является или становится частью стандарта, не означает, что люди автоматически знают ее точное имя и подпись наизусть. Конечно, вам также нужно знать, что означает _1_, но мне кажется, что это намного легче запомнить, по крайней мере, мне. Возможно, я просто недостаточно знаком с современными конструкциями C ++, но часть _2_ также требует много размышлений с моей стороны ... - person PieterNuyts; 18.03.2020
comment
Возможная проблема: в функции 'finally (F f)' он возвращает объект FinalAction, поэтому деконструктор может быть вызван перед окончательным возвратом функции. Возможно, нам стоит использовать std :: function вместо шаблона F. - person PieterNuyts; 18.03.2020

Кроме того, Основные рекомендации по C ++ наконец дали свои результаты.

Вот ссылка на реализацию GSL Microsoft и ссылку на Реализация Мартина Моэна

Бьярн Страуструп несколько раз говорил, что все, что есть в GSL, в конечном итоге должно войти в стандарт. Так что это должен быть перспективный способ использования finally.

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

В C ++ 11 RAII и лямбды позволяют окончательно сделать общее:

пример использования:

namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
    FinalAction(F f) : clean_{f} {}
   ~FinalAction() { if(enabled_) clean_(); }
    void disable() { enabled_ = false; };
  private:
    F clean_;
    bool enabled_{true}; }; }

template <typename F>
detail::FinalAction<F> finally(F f) {
    return detail::FinalAction<F>(f); }

вывод будет:

#include <iostream>
int main() {
    int* a = new int;
    auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; });
    std::cout << "doing something ...\n"; }

Лично я использовал это несколько раз, чтобы закрыть дескриптор файла POSIX в программе на C ++.

doing something...
leaving the block, deleting a!

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

Кроме того, мне он нравится больше, чем другие языки наконец, потому что при естественном использовании вы пишете код закрытия рядом с кодом открытия (в моем примере это новый и удалить ) и уничтожение следует за построением в порядке LIFO, как обычно в C ++. Единственным недостатком является то, что вы получаете автоматическую переменную, которую на самом деле не используете, а лямбда-синтаксис делает ее немного шумной (в моем примере в четвертой строке только слово finally и {} -block справа значимы, остальное по сути шум).

Другой пример:

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

 [...]
 auto precision = std::cout.precision();
 auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
 std::cout << std::setprecision(3);

Пример отключения:

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

//strong guarantee
void copy_to_all(BIGobj const& a) {
    first_.push_back(a);
    auto undo_first_push = finally([first_&] { first_.pop_back(); });

    second_.push_back(a);
    auto undo_second_push = finally([second_&] { second_.pop_back(); });

    third_.push_back(a);
    //no necessary, put just to make easier to add containers in the future
    auto undo_third_push = finally([third_&] { third_.pop_back(); });

    undo_first_push.disable();
    undo_second_push.disable();
    undo_third_push.disable(); }

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

#include <iostream>
int main() {
    int* a = new int;

    struct Delete_a_t {
        Delete_a_t(int* p) : p_(p) {}
       ~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
        int* p_;
    } delete_a(a);

    std::cout << "doing something ...\n"; }

Помимо упрощения очистки с помощью объектов на основе стека, RAII также полезен, потому что такая же «автоматическая» очистка происходит, когда объект является членом другого класса. Когда класс-владелец разрушается, ресурс, управляемый классом RAII, очищается, потому что в результате вызывается dtor для этого класса.

person Paolo.Bolzoni    schedule 26.08.2014
comment
Обратите внимание, что _1_ в основном то же самое, что и популярная идиома _2_, только с другим именем. - person user1633272; 01.12.2016
comment
Супер. Очень красивый стиль. Я также считаю, что мы могли бы использовать не только лямбду, но и наследование с виртуальным деструктором. - person anderas; 05.05.2017
comment
Вы потеряете тот факт, что код открытия и закрытия находится рядом, к тому же, если вам нужно написать свой класс, просто используйте RAII. - person kyb; 22.05.2017
comment
Насколько безопасна эта оптимизация? - person Paolo.Bolzoni; 23.05.2017
comment
В настоящее время его должны поддерживать все основные компиляторы. Что вас беспокоит? - person Nulano; 10.08.2017
comment
@ Paolo.Bolzoni Извините, что не ответил раньше, я не получил уведомление о вашем комментарии. Я беспокоился, что блок finally (в котором я вызываю функцию DLL) будет вызываться до конца области (потому что переменная не используется), но с тех пор я нашел вопрос по SO, который снял мои опасения. Я бы дал ссылку на него, но, к сожалению, больше не могу его найти. - person Paolo.Bolzoni; 11.08.2017
comment
Функция disable () - это своего рода бородавка на вашем чистом дизайне. Если вы хотите, чтобы метод finally вызывался только в случае сбоя, то почему бы просто не использовать оператор catch? Разве это не то, для чего? - person Nulano; 24.09.2017
comment
Возьмите мой пример, как бы вы написали код внутри улова? Внутри блока catch вы не знаете, какая копия не удалась: первая, вторая или третья? Вы, вероятно, можете немного проверить, но я считаю, что это более ясно. Вполне может быть дело вкуса и привычки. - person user2445507; 16.03.2019
comment
Имеет ли смысл удалить метод disable () и вызывать функцию из деструктора только при вызове во время обработки исключения? ~ FinalAction () {если (enabled_ && std :: uncaught_exceptions () ›0) clean_ (); } - person Paolo.Bolzoni; 17.03.2019
comment
Это очень хороший момент. +1 вам. Однако не так много людей проголосовали за вас. Надеюсь, вы не возражаете, что я отредактировал свой пост, добавив в него ваши комментарии. (Я, конечно, отдал вам должное.) Спасибо! :) - person smichak; 22.01.2020

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

почему даже управляемые языки предоставляют finally-блок, несмотря на то, что ресурсы в любом случае автоматически освобождаются сборщиком мусора?

person Michael Burr    schedule 02.10.2008
comment
Точно ... RAII кажется хорошим с идеальной точки зрения. Но мне все время приходится работать с обычными C API (например, с функциями C-стиля в Win32 API ...). Очень часто требуется получить ресурс, который возвращает какую-то HANDLE, для очистки которой затем требуется некоторая функция, например CloseHandle (HANDLE). Использование try ... finally - хороший способ справиться с возможными исключениями. (К счастью, похоже, что shared_ptr с настраиваемыми удалителями и лямбда-выражения C ++ 11 должны обеспечивать некоторое облегчение на основе RAII, которое не требует написания целых классов для обертывания некоторого API, который я использую только в одном месте.). - person Kevin; 02.10.2008

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

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

Тем не мение...

RAII переносит ответственность за безопасность исключений с пользователя объекта на дизайнера.

К сожалению, это его собственная ошибка. От старых привычек программирования на C трудно избавиться. Когда вы используете библиотеку, написанную на C или очень в стиле C, RAII использоваться не будет. Если не считать переписывания всего интерфейса API, это как раз то, с чем вам придется работать. Тогда отсутствие «наконец-то» действительно кусает.

Еще одна эмуляция блока "finally" с использованием лямбда-функций C ++ 11

person Philip Couling    schedule 21.09.2010
comment
@JamesJohnston, очень легко написать класс-оболочку, который будет содержать любой дескриптор и обеспечивать механику RAII. Например, ATL предоставляет множество из них. Кажется, вы считаете это слишком большой проблемой, но я не согласен, они очень маленькие и их легко написать. - person James Johnston; 03.10.2011
comment
Просто да, маленькое нет. Размер зависит от сложности библиотеки, с которой вы работаете. - person Mark Ransom; 04.01.2012
comment
@MarkRansom: есть ли какой-либо механизм, с помощью которого RAII может делать что-то интеллектуальное, если во время очистки возникает исключение, пока еще не обработано другое исключение? В системах с try / finally возможно - хотя и неудобно - организовать вещи так, чтобы ожидающее исключение и исключение, возникшее во время очистки, сохранялись в новом _1_. Есть ли какой-нибудь реальный способ добиться такого результата с помощью RAII? - person Philip Couling; 04.01.2012
comment
@supercat, в C ++ все еще хуже, исключение, генерируемое во время обработки исключений, может полностью выключить работу. См. c2.com/cgi/wiki?BewareOfExceptionsInTheDestructor. - person supercat; 01.12.2012
comment
@MarkRansom: Я несколько двояко отношусь к тому, что хуже - подход Java / .net, заключающийся в отказе от существующей инструкции, или подход C ++, заключающийся в принудительном отказе системы. Откровенно говоря, я считаю оба подхода довольно ужасными. Однако преимущество _1_ в том, что если остальная часть блока _2_ написана правильно, код внутри _3_ может узнать, ожидает ли исключение. Конечно, если бы язык мог просто предоставить _4_ конструкцию с любым ожидающим исключением (null, если нет), хранящимся в _5_, даже этот сценарий был бы намного чище. - person Mark Ransom; 01.12.2012
comment
@couling: Dispose не имеет ничего общего со сборщиком мусора, а скорее является детерминированным способом уведомления объектов, что их службы больше не требуются. Наличие явной очистки при успешном вызове и наличие метода очистки по умолчанию, предполагающего очистку от ошибки, если успешная часть не была запущена первой, в некоторой степени работоспособны, но это неприятно, поскольку случайное пропускание успешной части не может безопасно вызвать исключение ( который в противном случае был бы обычным методом уведомления кого-либо о существовании проблемы). - person supercat; 01.12.2012
comment
@couling: для ресурсов, которые могут реализовать последовательный откат, если семантика не зафиксирована, вероятно, было бы разумно, чтобы метод очистки по умолчанию выполнял откат любых незавершенных транзакций. Если пользовательский код случайно забывает фиксацию, последствия будут очевидны. Однако гораздо менее ясно, что следует делать с такими вещами, как файлы общего назначения, где обычная семантика _1_, как ожидается, будет вести себя по существу так же, как _2_. Если _3_ не может генерировать исключения, когда что-то идет не так, его нельзя использовать как обычную успешную очистку ... - person supercat; 03.12.2012
comment
@couling: ... но не совсем понятно, что еще он должен делать. Для файлов специального назначения можно иметь первую операцию записи, которая модифицирует файл, устанавливая в файле грязный флаг, который очищается только правильным Dispose, но такой подход требует, чтобы класс, реализующий его, знал о том, как грязный флаг, и т. д. (что может быть сложно, если несколько приложений могут получить доступ к разным частям файла одновременно). - person supercat; 03.12.2012
comment
@supercat ой, имел в виду Close, который имеет прямое отношение к сборщику мусора. Думал не на том языке ... см. Перезапись: - person supercat; 03.12.2012
comment
@supercat Интересно, как разные идиомы затрудняют сравнение. пример C ++ (на мой взгляд) сопоставим с выбросом RuntimeException на finalize; эффективно пытается вывести сборщик мусора из строя. Хороший выбор здесь - растопить. В java / .net я считаю, что лучше иметь отдельную очистку при ошибке для очистки при успехе, когда я откатываюсь от фиксации и игнорирую (только журнал) против исключения исключения. В результате я теперь наконец все реже и реже пользуюсь. Я считаю, что разработчик ни в коем случае не должен позволять этому вложенному исключению влиять на выполнение программы. - person Philip Couling; 04.12.2012
comment
@supercat Я понимаю, что написание одного и того же кода дважды таким способом кажется неприятным, но мой опыт показывает, что на практике блоки кода успеха и кода ошибки тонко различаются настолько часто, что я редко могу использовать, наконец, без ряда операторов finalize(). Из этих различий наиболее частым является обработка исключений. - person Philip Couling; 04.12.2012
comment
@couling: есть много случаев, когда программа вызывает метод if и хочет знать, (1) успешно ли он, (2) не удалось без побочных эффектов, (3) не удалось с побочными эффектами эффекты, с которыми вызывающий абонент готов справиться, или (4) потерпели неудачу с побочными эффектами, с которыми вызывающий абонент не может справиться. Только вызывающий будет знать, с какими ситуациями он может и не может справиться; звонящему нужен способ узнать, в какой ситуации. Жаль, что нет стандартного механизма для предоставления наиболее важной информации об исключении. - person Philip Couling; 04.12.2012
comment
@couling: Это не должно быть долго. Никто никогда не говорил, что нужно оборачивать всю библиотеку. Просто имейте класс SomeObject.DoSomething(), который инициализирует дескриптор в своем конструкторе, освобождает его в своем деструкторе и имеет класс _2_, чтобы вы могли использовать API. Возможно, это не самый лучший вариант, но это RAII для вашей библиотеки C, и он займет максимум ~ 20 строк (это может вызвать дополнительные изменения в вызывающем коде, и если вы считаете, что это слишком многословно, может быть уместно разыменование перегрузки) - person supercat; 04.12.2012
comment
Управляемые языки нуждаются в finally-блоках именно потому, что автоматически управляется только один вид ресурсов: память. RAII означает, что все ресурсы могут обрабатываться одинаково, поэтому нет необходимости в finally. Если бы вы действительно использовали RAII в своем примере (используя умные указатели в вашем списке вместо голых), код был бы проще, чем ваш finally-пример. И еще проще, если вы не проверяете возвращаемое значение new - проверка практически бессмысленна. - person Jasper; 21.03.2014

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

template <typename TCode, typename TFinallyCode>
inline void with_finally(const TCode &code, const TFinallyCode &finally_code)
{
    try
    {
        code();
    }
    catch (...)
    {
        try
        {
            finally_code();
        }
        catch (...) // Maybe stupid check that finally_code mustn't throw.
        {
            std::terminate();
        }
        throw;
    }
    finally_code();
}

Теперь мы можем написать такой код:

При желании вы можете заключить эту идиому в макрос «попробуйте - наконец»:

with_finally(
    [&]()
    {
        try
        {
            // Doing some stuff that may throw an exception
        }
        catch (const exception1 &)
        {
            // Handling first class of exceptions
        }
        catch (const exception2 &)
        {
            // Handling another class of exceptions
        }
        // Some classes of exceptions can be still unhandled
    },
    [&]() // finally
    {
        // This code will be executed in all three cases:
        //   1) exception was not thrown at all
        //   2) exception was handled by one of the "catch" blocks above
        //   3) exception was not handled by any of the "catch" block above
    }
);

Теперь в C ++ 11 доступен блок «finally»:

// Please never throw exception below. It is needed to avoid a compilation error
// in the case when we use "begin_try ... finally" without any "catch" block.
class never_thrown_exception {};

#define begin_try    with_finally([&](){ try
#define finally      catch(never_thrown_exception){throw;} },[&]()
#define end_try      ) // sorry for "pascalish" style :(

Лично мне не нравится «макро» версия идиомы «finally», и я бы предпочел использовать чистую функцию «with_finally», хотя синтаксис в этом случае более громоздкий.

begin_try
{
    // A code that may throw
}
catch (const some_exception &)
{
    // Handling some exceptions
}
finally
{
    // A code that is always executed
}
end_try; // Sorry again for this ugly thing

Вы можете протестировать приведенный выше код здесь: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813

PS

Если вам нужен блок finally в коде, тогда охрана с ограниченным объемом или ON_FINALLY/ON_EXCEPTION, вероятно, лучше подойдут для ваших нужд.

Вот краткий пример использования ON_FINALLY / ON_EXCEPTION:

Извините, что откопал такую ​​старую ветку, но есть серьезная ошибка в следующих рассуждениях:

void function(std::vector<const char*> &vector)
{
    int *arr1 = (int*)malloc(800*sizeof(int));
    if (!arr1) { throw "cannot malloc arr1"; }
    ON_FINALLY({ free(arr1); });

    int *arr2 = (int*)malloc(900*sizeof(int));
    if (!arr2) { throw "cannot malloc arr2"; }
    ON_FINALLY({ free(arr2); });

    vector.push_back("good");
    ON_EXCEPTION({ vector.pop_back(); });

    ...
person anton_rh    schedule 30.11.2017

RAII переносит ответственность за безопасность исключений с пользователя объекта на проектировщика (и разработчика) объекта. Я бы сказал, что это правильное место, поскольку тогда вам нужно только один раз получить правильную безопасность исключений (в дизайне / реализации). Используя finally, вам нужно обеспечить правильную безопасность исключений каждый раз, когда вы используете объект.

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

Конечно, сам список будет уничтожен при выходе из области видимости, но это не очистит созданные вами временные объекты.

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  finally
  {
    while (!myList.empty())
    {
      delete myList.back();
      myList.pop_back();
    }
  }
}

Вместо этого вы должны пойти по уродливому маршруту:

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

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  catch(...)
  {
  }

  while (!myList.empty())
  {
    delete myList.back();
    myList.pop_back();
  }
}

Подсказка: с помощью «finally» вы можете сделать больше, чем просто освобождение памяти.

FWIW, Microsoft Visual C ++, наконец, поддерживает try, и исторически он использовался в приложениях MFC как метод перехвата серьезных исключений, которые в противном случае привели бы к сбою. Например;

person Mephane    schedule 02.06.2010
comment
_1_ не возвращает NULL, вместо этого выдает исключение - person Myto; 02.06.2010
comment
Вы задаете важный вопрос, но на него есть 2 возможных ответа. Один из них - это то, что предоставлено Myto - использовать интеллектуальные указатели для всех динамических распределений. Другой - использовать стандартные контейнеры, которые всегда уничтожают свое содержимое при уничтожении. В любом случае, каждый выделенный объект в конечном итоге принадлежит статически выделенному объекту, который автоматически освобождает его при уничтожении. Очень жаль, что программистам трудно найти эти лучшие решения из-за высокой наглядности простых указателей и массивов. - person Hasturkun; 02.06.2010
comment
C ++ 11 улучшает это и включает _1_ и _2_ прямо в stdlib. - person j_random_hacker; 04.06.2010
comment
void DoStuff (константный вектор ‹string› & input) {list ‹shared_ptr ‹Foo›› myList; для (int i = 0; i ‹input.size (); ++ i) {auto tmp = make_shared ‹Foo› (input [i]); myList.push_back (tmp); } DoSomething (myList); } - person u0b34a0f6ae; 20.10.2011
comment
Причина, по которой ваш пример так ужасно выглядит, заключается не в том, что RAII ошибочен, а в том, что вы не смогли его использовать. Необработанные указатели не являются RAII. - person ebasconp; 18.10.2012
comment
Никто не собирается переписывать установленный код, чтобы использовать RAII в полном объеме, даже если они полностью понимают все его нюансы. Блок finally - полезная конструкция, которая позволяет программистам писать более короткий код, который выполняет необходимую очистку ресурсов, не вдаваясь в адские слои-абстракции. Гораздо приятнее - и более читабельно - поймать исключение, сделать что-то с ним, возможно, повторно выбросить его и знать, что метод finally всегда будет запускаться за вас, а не устанавливать какие-то флаги в catch {}, освобождать ресурсы, а затем проверять флаги и действующие на них. Ненавижу флаговый суп. - person Ben Voigt; 28.04.2013
comment
имейте в виду, что на самом деле это не исключения C ++, а исключения SEH. Вы можете использовать и то, и другое в коде MS C ++. SEH - это обработчик исключений ОС, который реализует исключения в VB и .NET. - person Jon; 08.08.2014

Я использовал это в прошлом, чтобы делать такие вещи, как сохранение резервных копий открытых файлов перед выходом. Однако некоторые настройки отладки JIT нарушают этот механизм.

int CMyApp::Run() 
{
    __try
    {
        int i = CWinApp::Run();
        m_Exitok = MAGIC_EXIT_NO;
        return i;
    }
    __finally
    {
        if (m_Exitok != MAGIC_EXIT_NO)
            FaultHandler();
    }
}

Как указано в других ответах, C ++ может поддерживать

int CMyApp::Run() 
{
    __try
    {
        int i = CWinApp::Run();
        m_Exitok = MAGIC_EXIT_NO;
        return i;
    }
    __finally
    {
        if (m_Exitok != MAGIC_EXIT_NO)
            FaultHandler();
    }
}
-подобные функции. Реализация этой функции, которая, вероятно, наиболее близка к тому, чтобы быть частью стандартного языка, - это реализация, сопровождающая Основные принципы C ++, набор рекомендаций по использованию C ++ под редакцией Бьярна Стауструпа и Херба Саттера. реализация _2_ является частью Библиотека поддержки рекомендаций (GSL). В Руководстве рекомендуется использовать _3_ при работе с интерфейсами в старом стиле, а также для него есть собственное руководство под названием Используйте объект final_action, чтобы выразить очистку, если не подходит доступен дескриптор ресурса.

person SmacL    schedule 02.10.2008
comment
и вы можете использовать SetUnhandledExceptionHandler для создания «глобального» обработчика невыявленных исключений - для исключений SEH. - person gbjbaanb; 05.10.2008
comment
SEH ужасен, а также предотвращает вызов деструкторов C ++ - person gbjbaanb; 05.10.2008
comment
Привет, я считаю, что мой ответ выше (stackoverflow.com/a/38701485/566849) полностью удовлетворяет вашим требованиям. - person paulm; 24.06.2013

Таким образом, C ++ не только поддерживает finally, но и рекомендуется использовать его во многих распространенных сценариях использования.

Пример использования реализации GSL будет выглядеть так:

Реализация и использование GSL очень похожи на те, что описаны в ответе Паоло Болзони. Одно отличие состоит в том, что объект, созданный gsl::finally(), не имеет вызова disable(). Если вам нужна эта функциональность (скажем, чтобы вернуть ресурс после его сборки и никаких исключений не должно произойти), вы можете предпочесть реализацию Паоло. В противном случае использование GSL максимально приближено к использованию стандартных функций.

#include <gsl/gsl_util.h>

void example()
{
    int handle = get_some_resource();
    auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });

    // Do a lot of stuff, return early and throw exceptions.
    // clean_that_resource will always get called.
}

У меня есть случай использования, когда я думаю, что finally должен быть вполне приемлемой частью языка C ++ 11, поскольку я думаю, что его легче читать с точки зрения потока. Мой вариант использования - это цепочка потоков потребитель / производитель, где в конце выполнения отправляется дозорный finally, чтобы закрыть все потоки.

person tobi_s    schedule 08.08.2018

Если бы C ++ поддерживал это, вы бы хотели, чтобы ваш код выглядел так:

Я думаю, что это более логично, чем помещать ваше объявление finally в начале цикла, поскольку оно происходит после выхода из цикла ... но это принятие желаемого за действительное, потому что мы не можем сделать это на C ++. Обратите внимание на то, что очередь downstream подключена к другому потоку, поэтому вы не можете поместить дозор push(nullptr) в деструктор downstream, потому что он не может быть уничтожен в этот момент ... он должен оставаться в живых, пока другой поток не получит nullptr.

    extern Queue downstream, upstream;

    int Example()
    {
        try
        {
           while(!ExitRequested())
           {
             X* x = upstream.pop();
             if (!x) break;
             x->doSomething();
             downstream.push(x);
           } 
        }
        finally { 
            downstream.push(nullptr);
        }
    }

Итак, вот как использовать класс RAII с лямбдой, чтобы сделать то же самое:

и вот как вы его используете:

    class Finally
    {
    public:

        Finally(std::function<void(void)> callback) : callback_(callback)
        {
        }
        ~Finally()
        {
            callback_();
        }
        std::function<void(void)> callback_;
    };

Не совсем, но вы можете в некоторой степени имитировать, например:

    extern Queue downstream, upstream;

    int Example()
    {
        Finally atEnd([](){ 
           downstream.push(nullptr);
        });
        while(!ExitRequested())
        {
           X* x = upstream.pop();
           if (!x) break;
           x->doSomething();
           downstream.push(x);
        }
    }
person Mark Lakata    schedule 01.12.2015
comment
Я рад, что вы упомянули, что блок finally может бросить; это то, что большинство ответов на использование RAII, похоже, игнорируют. Чтобы не писать блок finally дважды, можно сделать что-то вроде _1_ - person Fabio A.; 01.08.2016

Обратите внимание, что блок finally может сам вызвать исключение до того, как исходное исключение будет создано повторно, тем самым отбросив исходное исключение. Это то же самое поведение, что и в конечном блоке Java. Кроме того, вы не можете использовать return внутри блоков try & catch.

int * array = new int[10000000];
try {
  // Some code that can throw exceptions
  // ...
  throw std::exception();
  // ...
} catch (...) {
  // The finally-block (if an exception is thrown)
  delete[] array;
  // re-throw the exception.
  throw; 
}
// The finally-block (if no exception was thrown)
delete[] array;

Я придумал макрос

int * array = new int[10000000];
try {
  // Some code that can throw exceptions
  // ...
  throw std::exception();
  // ...
} catch (...) {
  // The finally-block (if an exception is thrown)
  delete[] array;
  // re-throw the exception.
  throw; 
}
// The finally-block (if no exception was thrown)
delete[] array;
, который можно использовать почти как ¹ ключевое слово return в Java; в нем используются _3_ и другие, лямбда-функции и _4_, поэтому требуется _5_ или выше; он также использует выражение составного оператора Расширение GCC, которое также поддерживается clang.

person bcmpinc    schedule 30.05.2014
comment
Это все, что я хотел знать! Почему ни один из других ответов не объяснил, что ловушка (...) + пустой бросок; работает почти как блок finally? Иногда это просто необходимо. - person sethobrien; 23.03.2015
comment
Решение, которое я предоставил в своем ответе (stackoverflow.com/a/38701485/566849), должно позволять генерировать исключения изнутри _1_ блок. - person VinGarcia; 21.07.2016
comment
Да, это был просто быстрый взлом, но если программист знает, что делает, он тем не менее может быть полезен. - person Fabio A.; 01.08.2016

ПРЕДУПРЕЖДЕНИЕ: в более ранней версии этого ответа использовалась другая реализация концепции со многими другими ограничениями.

Сначала давайте определим вспомогательный класс.

Тогда есть собственно макрос.

#include <future>

template <typename Fun>
class FinallyHelper {
    template <typename T> struct TypeWrapper {};
    using Return = typename std::result_of<Fun()>::type;

public:    
    FinallyHelper(Fun body) {
        try {
            execute(TypeWrapper<Return>(), body);
        }
        catch(...) {
            m_promise.set_exception(std::current_exception());
        }
    }

    Return get() {
        return m_promise.get_future().get();
    }

private:
    template <typename T>
    void execute(T, Fun body) {
        m_promise.set_value(body());
    }

    void execute(TypeWrapper<void>, Fun body) {
        body();
    }

    std::promise<Return> m_promise;
};

template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
    return FinallyHelper<Fun>(body);
}

Его можно использовать так:

#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try 
#define finally });                         \
        true;                               \
        ({return __finally_helper.get();})) \
/***/

Использование std::promise делает его очень простым в реализации, но, вероятно, также вносит немало ненужных накладных расходов, которых можно было бы избежать, повторно реализовав только необходимые функции из std::promise.

void test() {
    try_with_finally {
        raise_exception();
    }    

    catch(const my_exception1&) {
        /*...*/
    }

    catch(const my_exception2&) {
        /*...*/
    }

    finally {
        clean_it_all_up();
    }    
}

¹ ПРЕДОСТЕРЕЖЕНИЕ: есть несколько вещей, которые работают не так, как java-версия finally. С верхней части моей головы:


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

  1. после try должен быть хотя бы один catch() блок: это требование C ++;
  2. если функция имеет возвращаемое значение, отличное от void, но нет возврата в блоках try и catch()'s, компиляция завершится неудачно, потому что макрос finally расширится до кода, который захочет вернуть void. Это могло быть, ошибочно, недействительным из-за наличия макроса finally_noreturn.
  3. Как заявляли многие люди, решение состоит в том, чтобы использовать функции C ++ 11, чтобы избежать блоков finally. Одна из функций - finally.

невозможно выйти из внешнего цикла с помощью оператора break из блоков try и catch(), поскольку они находятся внутри лямбда-функции;

person Fabio A.    schedule 01.08.2016
comment
@MarkLakata, я обновил пост, добавив лучшую реализацию, которая поддерживает выдачу исключений и возврат. - person Fabio A.; 01.08.2016
comment
Выглядит неплохо. Вы можете избавиться от Caveat 2, просто вставив невозможный блок _1_ в начало макроса _2_, где xxx - это фиктивный тип только для того, чтобы иметь хотя бы один блок catch. - person Fabio A.; 02.08.2016
comment
@MarkLakata, я тоже думал об этом, но это сделало бы невозможным использование catch(xxx) {}, не так ли? - person Mark Lakata; 02.08.2016
comment
Я так не думаю. Просто создайте непонятный тип catch(...) в частном пространстве имен, которое никогда не будет использоваться. - person Fabio A.; 02.08.2016
comment
Gcc выплевывает xxx. Clang делает то же самое. Я считаю, что стандарт просто запрещает иметь _2_ блок посередине. - person Mark Lakata; 02.08.2016
comment
Теперь я понимаю вашу точку зрения (я пропустил ее в первый раз). Может быть, боги C ++ заявили, что этого не должно быть :) - person Fabio A.; 02.08.2016
comment
Это не работает, потому что весь смысл блока finally состоит в том, чтобы выполнить очистку, даже если код должен разрешить исключению покинуть блок кода. Подумайте: `try {// материал, который может бросить B} catch (A & a) {} finally {// если он есть в C ++ ... // то, что должно произойти, даже если B выброшено. } // не будет выполняться, если будет брошен B. `IMHO, точка исключения состоит в том, чтобы уменьшить код обработки ошибок, поэтому блоки catch, где бы ни произошел выброс, контрпродуктивны. Вот почему помогает RAII: при широком применении исключения имеют наибольшее значение на верхнем и нижнем уровнях. - person Mark Lakata; 02.08.2016

Вот ответ Мефана, написанный с использованием шаблонов RAII.

Еще одно введение в использование unique_ptr с контейнерами стандартной библиотеки C ++: здесь

#include <vector>
#include <memory>
#include <list>
using namespace std;

class Foo
{
 ...
};

void DoStuff(vector<string> input)
{
    list<unique_ptr<Foo> > myList;

    for (int i = 0; i < input.size(); ++i)
    {
      myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
    }

    DoSomeStuff(myList);
}

Я также считаю, что RIIA не является полностью полезной заменой обработки исключений и наличия файла finally. Кстати, я также считаю, что RIIA - плохая репутация. Я называю эти классы «дворниками» и использую их МНОГОЧИСЛЕННО. В 95% случаев они не инициализируют и не собирают ресурсы, они вносят какие-то изменения в определенную область действия или берут что-то уже настроенное и проверяют, что это уничтожено. Это официальное имя шаблона, одержимого Интернетом, меня ругают за то, что я даже предполагаю, что мое имя может быть лучше.

person Mark Lakata    schedule 09.07.2014

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

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

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

ИЗМЕНЕНО

person Dean Roddey    schedule 18.12.2018

Если вы не прерываете / продолжаете / не возвращаетесь и т. д., вы можете просто добавить уловку к любому неизвестному исключению и поместить за ним код Always. Это также происходит, когда вам не нужно генерировать исключение повторно.

Обычно, наконец, на других языках программирования обычно выполняется независимо от того, что (обычно означает независимо от любого возврата, прерывания, продолжения, ...) за исключением какой-то системы exit(), которая сильно отличается в зависимости от языка программирования - например, PHP и Java просто завершают работу в этот момент, но Python все равно выполняется, а затем завершается.

try{
   // something that might throw exception
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called always,
//don't forget that it might throw some exception too
doSomeCleanUp(); 

Также смотрите ответы в: stackoverflow.com/questions/7779652/

Но код, который я описал выше, не работает таким образом
= ›следующий код выводит ТОЛЬКО something wrong!:

Так в чем проблема?

#include <stdio.h>
#include <iostream>
#include <string>

std::string test() {
    try{
       // something that might throw exception
       throw "exceptiooon!";

       return "fine";
    } catch( ... ){
       return "something wrong!";
    }
    
    return "finally";
}

int main(void) {
    
    std::cout << test();
    
    
    return 0;
}
person jave.web    schedule 02.09.2015
comment
@burlyearly, хотя ваше мнение не является святым, я понимаю, но в C ++ такого нет, поэтому вы должны рассматривать его как верхний слой, который имитирует такое поведение. - person burlyearly; 21.12.2016
comment
DOWNVOTE = ПОЖАЛУЙСТА, КОММЕНТАРИЙ :) - person jave.web; 22.12.2016
comment
@burlyearly извиняюсь за святое упоминание, позже я узнал, что святое также может означать смирение :) - person jave.web; 05.10.2019
comment
Симпатичная идиома, но не совсем то же самое. возврат в блоке try или catch не пройдет через ваш код 'finally:'. - person jave.web; 06.11.2020

person    schedule
comment
Этот неправильный ответ (с рейтингом 0) стоит оставить, так как Эдвард Кметт поднимает очень важное различие. - person Edward KMETT; 23.04.2010
comment
Еще больший недостаток (IMO): этот код съедает все исключения, чего _1_ не делает. - person Mark Lakata; 04.12.2012
comment

- person Ben Voigt; 28.04.2013