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

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

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

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

Также смотрите ответы в:stackoverflow.com/questions/7779652/…
18446744073709551615 16.11.2015 11:03
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
290
1
237 085
16

Ответы 16

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

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

Обычно 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.

«Разрушение - это отказ от ресурсов» - DIRR ... Нет, у меня не работает. = P

Erik Forbes 02.10.2008 11:37

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

Kevin 02.10.2008 11:44

Действительно плохое имя, я все еще не могу понять его.

user15071 02.10.2008 12:19

Я согласен с названием - жаль, что мы застряли с ним.

Michael Burr 02.10.2008 23:34

SBRM == Управление ресурсами, связанными с областью действия

Johannes Schaub - litb 25.11.2008 00:54

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

Hardryv 10.04.2012 00:47

@Kevin: Во время использования RAII действительно не похож на using: вы не можете объединить оператор using с инициализатором / финализатором вашего класса. Вы должны быть точными и не забывать using-утверждение. Вложение становится ужасным. Варианты использования using по сравнению с RAII в основном равны нулю: блокировки с ограниченным объемом, векторы, строки, общие указатели, транзакции и все остальное без необходимости вручную писать using(). Как вы это представляете, using - плохая защита перед RAII, и я понимаю, что многие разработчики .net изо всех сил пытаются убедить себя, что RAII похож, но в глубине души они (должны) знать, что это не так.

Sebastian Mach 06.07.2012 15:40

Это заставляет вас застрять, когда вам нужно что-то очистить, что не соответствует времени жизни любого объекта C++. Я предполагаю, что вы получите Lifetime Equals C++ Class Liftime Or Else It Gets Ugly (LECCLEOEIGU?).

Warren P 27.12.2012 20:16

Я думаю о чем-то похожем на Уоррена: подождите, что произойдет, если у вас есть код, который должен обязательно выполняться, но не в конце жизненного цикла объекта? Разве еще не существует варианта использования слова «finally», и поэтому его отсутствие в C++ - это дыра в пространстве дизайна? Кроме того, использование деструктора означает, что C++ всегда должен реализовываться с использованием ДЕТЕРМИНИСТИЧЕСКОГО времени жизни памяти. Ни одна из будущих версий C++ не сможет использовать вместо этого сборку мусора, потому что все полагаются на своевременное выполнение деструкторов.

ToolmakerSteve 21.11.2013 06:30

@WarrenP Тогда вы должны обернуть это "что-то" в класс. В этом вся суть RAII, и на самом деле он часто заставляет вас делать лучший дизайн.

Jasper 21.03.2014 15:16

@ToolMakerSteve То же самое и с вами, заверните. Это также не дыра в дизайне, поскольку, если вы хотите сделать gc на C++, вы, вероятно, сделаете это с необработанными указателями (или, может быть, с конкретным классом указателя, но тогда это еще менее актуально), а удаление всегда происходит перед gc может вмешаться. Другими словами, это повлияет только на сломанный (/ утечку) текущий код, в котором вы уже не могли полагаться на конструкторы.

Jasper 21.03.2014 15:17

@Jasper: Вот почему в каждой кодовой базе так много почти совершенно бесполезных классов. Потому что C++ не оставляет вам другого выбора. Это называется языковой бородавкой.

Warren P 21.03.2014 17:15

@WarrenP Нет, это называется принцип единственной ответственности.

Jasper 21.03.2014 17:21

Мне всегда нравится, что единственная ответственность класса состоит в том, что он имеет большую пропускную способность, чем «бумага над ограничениями этого языка».

Warren P 21.03.2014 17:34

Что ж, лично я предпочитаю, когда язык решает не творить магию, а вместо этого дает вам мощные инструменты, чтобы делать что-то самостоятельно, и сообщая, что единственный способ сделать это - поддерживаемый и масштабируемый способ, который решает проблему внутри кода. честно говоря, для меня не проблема. Посмотрите на альтернативы, Java передает это в руки вызывающего кода с finally, C# дает тот же метод и некоторую магию через using, языки сценариев часто дают вам аналогичные решения, но также говорят, что ограниченное время выполнения должно предотвратить большие проблемы. Я бы взял RAII в любой день.

Jasper 21.03.2014 21:41

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

ToolmakerSteve 22.03.2014 23:38

@Jasper: Я удалил один из своих комментариев - решил, что добавление недетерминированный GC в C++ сделает и без того слишком сложный язык еще более головокружительным. Настоящая проблема заключается в том, что годы, которые я программировал на C++, до появления Java или .Net, я считал это неприятным - и все еще чувствую, когда мне нужно его использовать. Я бы хотел увидеть новое изобретение C++, начиная с нуля, которое было бы таким же чистым, как python или C#, с библиотекой классов, основанной на RAII, интеллектуальных указателях и / или слабых ссылках: решения детерминированный GC. Да, все, что существует в C++, но более чистый язык для размышлений. По общему признанию, не по теме ...

ToolmakerSteve 23.03.2014 01:56

@ToolmakerSteve Перед этим удалил один из моих ответов на него: P, когда я понял, что совершал ошибку. В любом случае, я согласен с тем, что чистый неуправляемый язык был бы замечательным (или, возможно, даже мог бы быть хороший способ смешать управляемый и неуправляемый). Даже Бьярн Соуструп в основном сказал то же самое (теперь он предпочел бы не иметь багажа C-совместимости, но он не может изменить это сейчас). Я обнаружил, что необходимость думать о владении - это хорошо. Конечно, вы почти всегда должны использовать вещи C++ вместо C-вещей, что не всегда случается и не всегда было возможно в те времена, когда Java еще не существовала.

Jasper 23.03.2014 02:34

Лично я не считаю, что RAll действительно хорошая альтернатива блоку finally. Это просто означает, что для правильной очистки вы должны заключить ресурсы в искусственные объекты, чтобы получить автоматическую очистку, как указал Уоррен П. в своем комментарии выше (Language wart).

Devolus 18.07.2014 21:18

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

Jan Smrčina 25.04.2015 22:12

Да, потому что это намного меньше кода, чем <snark> mutex_.lock (); попробуйте {foobar (); } наконец {mutex_, unlock (); } </snark> объекты-оболочки, шаблоны, мальчик, этот C++ 11, конечно, намного проще!

Tony BenBrahim 23.05.2015 01:03

@TonyBenBrahim Умножается на всю вашу базу кода, применяется ко всем ресурсам, для которых вам нужна детерминированная очистка? да. Да, это так.

Cubic 04.06.2015 23:25

@Cubic, да, теперь я это понимаю. Однако требует некоторого привыкания, исходящего от Java.

Tony BenBrahim 06.06.2015 02:16
RAII? Isn't that the organization that sued Napster and a bunch of other producers of file sharing tools?
Damian Yerrick 24.09.2015 18:24

@Jasper Я могу придумать ситуацию (возможно, необычную, но я не знаю), в которой, вероятно, было бы чище иметь хотя бы try...finally, при условии, что он не нужен в нескольких местах: Работа напрямую с оборудованием. Предположим, что определенная ячейка памяти обычно должна иметь флаг "все очищено", но этот флаг следует изменять во время определенных операций. Если эти операции выполняются одной функцией, было бы чище иметь try { uintX_t* flag = some_address; *flag = WHATEVER; /* ... */ } finally { *flag = ALL_CLEAR; },

Justin Time - Reinstate Monica 22.12.2016 07:45

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

Justin Time - Reinstate Monica 22.12.2016 07:48

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

Justin Time - Reinstate Monica 22.12.2016 07:50

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

Justin Time - Reinstate Monica 22.12.2016 08:05

@Jan Этого нет в стандартной библиотеке C++, но есть BOOST_SCOPE_EXIT, если вы можете простить синтаксический шаблон, и что тело вашего кода не должно бросать. boost.org/doc/libs/1_65_1/libs/scope_exit/doc/html/scope_exi‌ t /…

Max Barraclough 03.11.2017 14:41

нам нужно наконец, точка.

Marc 30.11.2017 12:18

Нет, мы этого не делаем. Период.

Fernando Silveira 19.11.2018 21:58

Немного сложное объяснение после того, как вы написали основную идею разрушения, чтобы освободить память (это было более ясно до того, как я начал читать последнюю часть ответа), но все равно спасибо!

Ivan Silkin 04.10.2019 00:07

Я согласен с тем, что RAII элегантно решает проблему обеспечения освобождения ресурсов. Это не решает изящным образом, гарантируя, что что-то всегда будет выполняться (например, ведение журнала).

Nicolas Bousquet 06.04.2020 17:09

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

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

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

имейте в виду, что на самом деле это не исключения C++, а исключения SEH. Вы можете использовать и то, и другое в коде MS C++. SEH - это обработчик исключений ОС, который реализует исключения в VB и .NET.

gbjbaanb 05.10.2008 03:13

и вы можете использовать SetUnhandledExceptionHandler для создания «глобального» обработчика невыявленных исключений - для исключений SEH.

gbjbaanb 05.10.2008 03:14

SEH ужасен, а также предотвращает вызов деструкторов C++

paulm 24.06.2013 14:30

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

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

Это очень хороший момент. +1 вам. Однако не так много людей проголосовали за вас. Надеюсь, вы не возражаете, что я отредактировал свой пост, включив в него ваши комментарии. (Я, конечно, отдал вам должное.) Спасибо! :)

Kevin 02.10.2008 20:22

В C++, наконец, требуется НЕТ из-за RAII.

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

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

Пример:

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

C++ как RAII

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 значительно упрощает правильное использование объекта БД. Объект DB будет правильно закрываться с помощью деструктора, как бы мы ни пытались злоупотребить им.

Java Like, наконец

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

// Make sure not to throw exception if one is already propagating. Для деструкторов C++ также важно не генерировать исключения именно по этой причине.

Cemafor 24.05.2013 01:28

@Cemafor: причина, по которой C++ не выбрасывает исключения из деструктора, отличается от Java. В Java это будет работать (вы просто потеряете исходное исключение). В C++ это действительно плохо. Но суть C++ в том, что вам нужно сделать это только один раз (разработчиком класса), когда он пишет деструктор. В Java это нужно делать в момент использования. Таким образом, пользователь класса несет ответственность за то, чтобы в любое время написать одну и ту же шаблонную пластину.

Martin York 24.05.2013 04:31

Если дело в том, что вы «нужны», вам тоже не нужен RAII. Избавимся от этого! :-) Шутки в сторону, RAII подходит во многих случаях. RAII делает более громоздким те случаи, когда вы хотите выполнить некоторый код (не связанный с ресурсами), даже если приведенный выше код был возвращен раньше. Для этого вы либо используете gotos, либо разделяете его на два метода.

Trinidad 09.11.2014 20:30

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

Martin York 09.11.2014 21:56

@LokiAstari О, это довольно просто, представьте, например, что я создаю структуру данных из файла XML и хочу распечатать ее в конце функции. Дело в том, что в моем коде есть куча return в загрузочной части, поэтому мне не придется делать много if в будущем. RAII был бы очень громоздким, так как мне потребовалось бы создать класс только для того, чтобы сделать красивую печать, нелепо. Либо вы используете goto вместо return, либо отделяете загрузку от печати как разные функции. Я согласен, возможно, это не лучший пример, но нелепо думать, что RAII = наконец

Trinidad 10.11.2014 04:54

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

Martin York 10.11.2014 06:34

наконец, java 7+: попробуйте (DB db = new DB ("DBDesciptionString")) {// используйте db}

Tony BenBrahim 23.05.2015 01:12

@TonyBenBrahim: По крайней мере, Java движется к C++ небольшими шагами. Но это принятие директивы использования C#. Шаг, но не такой мощный, как RAII ;-)

Martin York 23.05.2015 02:43

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

peterh - Reinstate Monica 23.11.2016 01:23

Итак, если деструкторы C++ не могут генерировать исключения и не имеют возвращаемых значений, как, черт возьми, вы должны сообщать об ошибках? Например, если деструктору соединения с базой данных необходимо зафиксировать или откатить незавершенную транзакцию, или если деструктору файлового потока необходимо очистить буфер, и эта операция завершится неудачно по какой-либо причине. Вы можете решить эту проблему, введя явную функцию очистки (close / dispose / disconnect / etc.), Которая возвращает код ошибки, но в основном это делает бесполезными неявные вызовы деструктора C++.

dan04 10.02.2017 20:46

@ dan04: деструкторы C++ МОЖЕТ генерируют исключения. Обычно это не самая лучшая идея. Они просто не могут сгенерировать исключение, когда исключение уже распространяется. См .: stackoverflow.com/questions/130117/… и codereview.stackexchange.com/questions/540/… (прочтите последний комментарий к этому).

Martin York 11.02.2017 06:21

@ dan04 Для этих случаев есть другая идиома. Это называется защитой прицела. Страж области видимости принимает лямбда-указатель / указатель на функцию / все, что вы хотите, при построении и выполняет его при уничтожении. Это позволяет вам определить раздел кода, который эффективно работает как блок finally, за исключением того, что код очистки можно легко отключить, изменить для других областей или внедрить в другие места. Я вообще не рекомендую использовать исключения в любом неявно вызываемом коде (деструкторах IE), так как это может привести к возникновению исключений в непредвиденное время.

LivePastTheEnd 26.04.2017 04:15

Критика «НЕ требуется из-за RAII»: существует множество случаев, когда добавление специального RAII было бы слишком большим количеством шаблонного кода для добавления, а попытка наконец-то была бы просто чрезвычайно уместной.

ceztko 10.03.2019 22:52

@ceztko Я бы сказал, что try / finally использует гораздо более bolierplate. Проблема в том, что try / finally нужно выполнять каждый раз, когда он используется (см. Пример выше). Хотя RAII должен быть выполнен первоначальным автором только один раз. Другая проблема с try / finally заключается в том, что вы перекладываете бремя правильного использования с автора на пользователя. Заставить пользователя делать это каждый раз правильно намного сложнее, чем заставить автора делать это правильно один раз.

Martin York 11.03.2019 11:10

@MartinYork Я не говорю, что вместо RAII всегда следует использовать try-finally. Вы правы в том, что API-интерфейсы должны предоставлять соответствующие деструкторы для своих структур или обеспечивать защиту RAII, где это необходимо (например, для обработки транзакций). Я говорю о том, что в конечном пользовательском коде есть некоторые обстоятельства, когда добавление RAII - не самый надежный инструмент, который он, возможно, захочет использовать, и попытка наконец-то выполнит свою работу без создания скучных специальных структур для RAII. Короче говоря: было бы хорошо иметь и то, и другое, и мудрый программист использовал бы наиболее подходящие в каждой ситуации.

ceztko 11.03.2019 11:37

@MartinYork Иногда различие «пользователь против автора» просто не применяется. Скажем, в конце моего main() я хочу записать файл с надписью ERROR или OK, в зависимости от того, что произошло. Я бы сделал что-нибудь вроде bool ok = false; try { /* do stuff */; ok = true; } catch (...) { ... } finally { write_my_file(ok); }. Теперь мне нужно написать для этого класс, и я становлюсь «автором» класса, который мне не понадобился бы, если бы я мог исправить это как «пользователь».

PieterNuyts 28.02.2020 13:23

@PieterNuyts Верно. Но, по-вашему, каждый раз, когда вы захотите использовать этот шаблон, вам придется вручную добавлять все вышеперечисленные шаблоны (и не забудьте сделать это правильно и проверить, что он работает правильно). По-моему, вы делаете это один раз в классе. Теперь вы можете протестировать его один раз (он навсегда останется верным после тестирования), и вы можете повторно использовать шаблон, просто объявив объект в правильной области. Так что, если вы сделаете это только один раз, то, конечно, может получиться проще. Но как часто вы хоть раз в жизни что-то делаете? Существуют также библиотеки, которые просто выполняют лямбду при уничтожении.

Martin York 28.02.2020 22:20

@PieterNuyts bool ok = false; gsl::final_act finalAction([&ok](){write_my_file(ok);}); /* do stuff */; ok = true; Мне кажется, он намного менее напыщенный.

Martin York 28.02.2020 22:22

@MartinYork, На первый взгляд, ваш фрагмент кода примерно такой же длинный, как и мой (и вы пропустили блок try / catch, который все еще понадобится), поэтому я не уверен, почему вы считаете его менее сложным. Но любой, кто хочет понять вашу, должен знать или искать, что такое gsl::final_act и как именно он работает.

PieterNuyts 02.03.2020 12:03

@PieterNuyts Все дело в том, что вам не нужен блок try / catch (или наконец), поскольку все, что будет сделано, - это соответствующий деструктор. В добавляемом мной примере finally есть простой альтернативный метод обеспечения функциональности finally, если вы действительно этого хотите. Обычно вы этого не делаете, но его просто добавить в C++. Что касается знания того, что делает final_act: переход к ближайшему к вам стандарту (C++ gsl => modernescpp.com/index.php/…)

Martin York 03.03.2020 03:18

@PieterNuyts Вот как я это вижу. Более чем счастлив обсудить эту тему, если хотите.

Martin York 03.03.2020 03:46

Никакой деструктор не может заменить блок catch, поскольку деструкторы не могут перехватывать исключения, созданные извне. Таким образом, вы можете отбросить блок finally, но вам все равно понадобится try / catch (конечно, при условии, что вы хотите перехватить и обработать исключение).

PieterNuyts 18.03.2020 21:59

Вы правы, что я не учел исключения внутри блока finally. Итак, ваш код, возможно, немного короче. Тем не менее, то, что какая-то функция является или становится частью стандарта, не означает, что люди автоматически знают ее точное имя и подпись наизусть. Конечно, вам также нужно знать, что означает finally, но мне кажется, что это намного легче запомнить, по крайней мере, мне. Возможно, я просто недостаточно знаком с современными конструкциями C++, но часть [&ok]() также требует от меня много размышлений ...

PieterNuyts 18.03.2020 22:47

try
{
  ...
  goto finally;
}
catch(...)
{
  ...
  goto finally;
}
finally:
{
  ...
}

Симпатичная идиома, но не совсем то же самое. возврат в блоке try или catch не пройдет через ваш код 'finally:'.

Edward KMETT 23.04.2010 23:48

Этот неправильный ответ (с рейтингом 0) стоит оставить, так как Эдвард Кметт поднимает очень важное различие.

Mark Lakata 04.12.2012 11:21

Еще больший недостаток (IMO): этот код съедает все исключения, чего не делает finally.

Ben Voigt 28.04.2013 02:19

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

RAII moves the responsibility of exception safety from the user of the object to the designer (and implementer) of the object. I would argue this is the correct place as you then only need to get exception safety correct once (in the design/implementation). By using finally you need to get exception safety correct every time you use an object.

Чаще всего вам приходится иметь дело с динамически выделяемыми объектами, динамическим числом объектов и т. д. В блоке 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();
    }
  }
}

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

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

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-блок, несмотря на то, что ресурсы в любом случае автоматически освобождаются сборщиком мусора?

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

Управляемые языки нуждаются в finally-блоках именно потому, что автоматически управляется только один вид ресурсов: память. RAII означает, что все ресурсы могут обрабатываться одинаково, поэтому нет необходимости в finally. Если бы вы действительно использовали RAII в своем примере (используя умные указатели в вашем списке вместо голых), код был бы проще, чем ваш «finally» -пример. И еще проще, если вы не проверяете возвращаемое значение new - проверка практически бессмысленна.

Myto 02.06.2010 18:59

new не возвращает NULL, вместо этого выдает исключение

Hasturkun 02.06.2010 19:07

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

j_random_hacker 04.06.2010 06:53

C++ 11 улучшает это и включает std::shared_ptr и std::unique_ptr непосредственно в stdlib.

u0b34a0f6ae 20.10.2011 01:33

void DoStuff (const vector <string> & input) {список <shared_ptr <Foo>> myList; for (int i = 0; i <input.size (); ++ i) {auto tmp = make_shared <Foo> (input [i]); myList.push_back (tmp); } DoSomething (myList); }

ebasconp 18.10.2012 03:29

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

Ben Voigt 28.04.2013 02:18

Никто не собирается переписывать установленный код, чтобы использовать RAII в полном объеме, даже если они полностью понимают все его нюансы. Блок finally - полезная конструкция, которая позволяет программистам писать более короткий код, который выполняет необходимую очистку ресурсов, не углубляясь в «адские слои-абстракции». Гораздо приятнее - и более читабельно - поймать исключение, что-то с ним сделать, возможно, повторно выбросить и знать, что метод finally всегда будет запускаться за вас, а не устанавливать какие-то флаги в catch {}, освобождать ресурсы, а затем проверять флаги и действующие на них. Ненавижу флаговый суп.

Jon 08.08.2014 19:39

why is it that even managed languages provide a finally-block despite resources being deallocated automatically by the garbage collector anyway?

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

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

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

RAII moves the responsibility of exception safety from the user of the object to the designer

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

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

James Johnston 03.10.2011 22:37

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

Mark Ransom 04.01.2012 07:06

Просто да, маленькое нет. Размер зависит от сложности библиотеки, с которой вы работаете.

Philip Couling 04.01.2012 18:20

@MarkRansom: есть ли какой-либо механизм, с помощью которого RAII может делать что-то интеллектуальное, если во время очистки возникает исключение, пока еще не обработано другое исключение? В системах с try / finally возможно - хотя и неудобно - организовать все так, чтобы ожидающее исключение и исключение, возникшее во время очистки, сохранялись в новом CleanupFailedException. Есть ли какой-нибудь реальный способ добиться такого результата с помощью RAII?

supercat 01.12.2012 02:36

@supercat, в C++ все еще хуже, исключение, генерируемое во время обработки исключений, может полностью выключить работу. См. c2.com/cgi/wiki?BewareOfExceptionsInTheDestructor

Mark Ransom 01.12.2012 03:23

@MarkRansom: Я несколько двояко отношусь к тому, что хуже - подход Java / .net, заключающийся в отказе от существующей инструкции, или подход C++, заключающийся в принудительном отказе системы. Откровенно говоря, я считаю оба подхода довольно ужасными. Однако преимущество finally в том, что если остальная часть блока try написана правильно, код в finally может узнать, ожидает ли исключение. Конечно, если бы язык мог просто предоставить конструкцию finally Exception ex с любым ожидающим исключением (null, если нет), хранящимся в ex, даже этот сценарий был бы намного чище.

supercat 01.12.2012 03:34

@couling: Dispose не имеет ничего общего со сборщиком мусора, а скорее является детерминированным способом уведомления объектов, что их услуги больше не требуются. Наличие явного вызова «очистка при успехе» и наличие метода очистки по умолчанию, предполагающего, что очистка выполняется на предмет ошибки, если часть «успех» не запускалась первой, в некоторой степени работоспособны, но это неприятно, поскольку случайное пропускание части «успех» не может безопасно вызвать исключение (в противном случае это был бы обычный метод уведомления кого-либо о существовании проблемы).

supercat 03.12.2012 09:13

@couling: для ресурсов, которые могут реализовать последовательную семантику «откат, если не зафиксировано», вероятно, было бы разумно, чтобы метод очистки по умолчанию выполнял откат любых незавершенных транзакций. Если пользовательский код случайно забывает фиксацию, последствия будут очевидны. Однако гораздо менее ясно, что следует делать с такими вещами, как файлы общего назначения, где обычная семантика Dispose, как ожидается, будет вести себя по существу так же, как Close. Если Dispose не может генерировать исключения, когда что-то идет не так, его нельзя использовать как обычную «успешную» очистку ...

supercat 03.12.2012 20:10

@couling: ... но не совсем понятно, что еще он должен делать. Для файлов специального назначения можно иметь первую операцию записи, которая изменяет файл, устанавливая в файле флаг «грязный», который сбрасывается только «правильным» Close, но такой подход требует, чтобы класс, реализующий его, знал о том, как флаг грязи и т. д. будет работать (что может быть сложно, если несколько приложений могут получить доступ к разным частям файла одновременно).

supercat 03.12.2012 20:15

@supercat ой, имел в виду finalize, который имеет прямое отношение к GC. Думал не на том языке ... см. Перезапись:

Philip Couling 04.12.2012 00:17

@supercat Интересно, как разные идиомы затрудняют сравнение. пример C++ (на мой взгляд) сопоставим с выбросом RuntimeException на finalize(); эффективно пытается вывести сборщик мусора из строя. Хороший выбор здесь - растопить. В java / .net я считаю, что лучше иметь отдельную «очистку при ошибке» на «очистку при успехе», где я откатываюсь от фиксации и игнорирую (только журнал) против исключения исключения. В результате я теперь наконец все реже и реже пользуюсь. Я считаю, что разработчик ни в коем случае не должен позволять этому «вложенному» исключению влиять на выполнение программы.

Philip Couling 04.12.2012 00:19

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

Philip Couling 04.12.2012 00:26

@couling: есть много случаев, когда программа вызывает метод SomeObject.DoSomething() и хочет знать, (1) успешно ли он, (2) не удалось без побочных эффектов, (3) не удалось с побочными эффектами, с которыми вызывающий готов справиться, или ( 4) не удалось с побочными эффектами, с которыми вызывающий абонент не может справиться. Только вызывающий будет знать, с какими ситуациями он может и не может справиться; звонящему нужен способ узнать, в какой ситуации. Жаль, что нет стандартного механизма для предоставления наиболее важной информации об исключении.

supercat 04.12.2012 00:43

@couling: Это не должно быть долго. Никто никогда не говорил, что нужно обернуть всю библиотеку. Просто имейте класс MyResource, который инициализирует дескриптор в своем конструкторе, освобождает его в своем деструкторе и имеет класс getHandle(), чтобы вы могли использовать API. Возможно, это не самый приятный вариант, но это RAII для вашей библиотеки C, и он займет максимум ~ 20 строк (это может вызвать больше изменений в вызывающем коде, и если вы думаете, что это слишком многословно, может быть уместно разыменование перегрузки)

Jasper 21.03.2014 16:30

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

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;

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

Я рад, что вы упомянули, что блок finally может бросить; это то, что большинство ответов «использовать RAII», кажется, игнорируют. Чтобы не писать блок finally дважды, можно сделать что-нибудь вроде std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);.

sethobrien 23.03.2015 20:14

Это все, что я хотел знать! Почему ни один из других ответов не объяснил, что ловушка (...) + пустой бросок; работает почти как блок finally? Иногда это просто необходимо.

VinGarcia 21.07.2016 00:06

Решение, которое я предоставил в своем ответе (stackoverflow.com/a/38701485/566849), должно позволять генерировать исключения из блока finally.

Fabio A. 01.08.2016 17:39

Как заявляли многие люди, решение состоит в том, чтобы использовать функции C++ 11, чтобы избежать блоков finally. Одна из функций - unique_ptr.

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

#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);
}

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

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

Кроме того, C++ Основные рекомендации дают наконец.

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

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

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

В 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"; }

вывод будет:

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

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

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

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

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

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

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

Пример запрещать:

//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, у вас все еще может быть наконец, но код становится немного более длинным. Просто определите структуру только с конструктором и деструктором, конструктор будет ссылаться на все необходимое, а деструктор выполнит необходимые действия. Это в основном то, что делает лямбда, выполняемая вручную.

#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"; }

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

Возможная проблема: в функции 'finally (F f)' он возвращает объект FinalAction, поэтому деконструктор может быть вызван перед окончательным возвратом функции. Возможно, нам стоит использовать std :: function вместо шаблона F.

user1633272 01.12.2016 17:05

Обратите внимание, что FinalAction в основном совпадает с популярной идиомой ScopeGuard, только с другим именем.

anderas 05.05.2017 11:16

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

kyb 22.05.2017 15:45

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

Paolo.Bolzoni 23.05.2017 17:46

Насколько безопасна эта оптимизация?

Nulano 10.08.2017 15:20

В настоящее время его должны поддерживать все основные компиляторы. Что вас беспокоит?

Paolo.Bolzoni 11.08.2017 12:06

@ Paolo.Bolzoni Извините, что не ответил раньше, я не получил уведомления о вашем комментарии. Я беспокоился, что блок finally (в котором я вызываю функцию DLL) будет вызываться до конца области (поскольку переменная не используется), но с тех пор я нашел вопрос по SO, который снял мои опасения. Я бы дал ссылку на него, но, к сожалению, больше не могу его найти.

Nulano 24.09.2017 18:10

Функция disable () - это своего рода бородавка на вашем чистом дизайне. Если вы хотите, чтобы метод finally вызывался только в случае сбоя, то почему бы просто не использовать оператор catch? Разве это не для этого?

user2445507 16.03.2019 02:39

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

Paolo.Bolzoni 17.03.2019 10:26

Имеет ли смысл удалить метод disable () и вызывать функцию из деструктора только при вызове во время обработки исключения? ~ FinalAction () {если (enabled_ && std :: uncaught_exceptions ()> 0) clean_ (); }

smichak 22.01.2020 17:20

ИЗМЕНИТЬ

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

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(); 

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

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

Но код, который я описал выше, так не работает.
=> следующий код выводит ТОЛЬКО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;
}

Это не работает, потому что весь смысл блока finally заключается в выполнении очистки, даже если код должен позволяет исключению покинуть блок кода. Подумайте: `try {// что-то, что может бросить" B "} catch (A & a) {} finally {// если бы это было в C++ ... // что-то, что должно произойти, даже если выброшено" B ". } // не будет выполняться, если будет выброшено "B". `IMHO, точка исключений - это код обработки ошибок уменьшать, поэтому блоки catch, где бы ни произошел выброс, контрпродуктивны. Вот почему помогает RAII: при широком применении исключения имеют наибольшее значение на верхнем и нижнем уровнях.

burlyearly 21.12.2016 18:07

@burlyearly хотя ваше мнение не является святым, я понимаю, но в C++ такого нет, поэтому вы должны рассматривать его как верхний слой, который имитирует это поведение.

jave.web 22.12.2016 17:35

DOWNVOTE = ПОЖАЛУЙСТА, КОММЕНТАРИЙ :)

jave.web 05.10.2019 14:31

@burlyearly извиняюсь за святое упоминание, позже я узнал, что святое также может означать смирение :)

jave.web 06.11.2020 17:13

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

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

    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);
        }
    }

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

Привет, я считаю, что мой ответ выше (stackoverflow.com/a/38701485/566849) полностью удовлетворяет вашим требованиям.

Fabio A. 01.08.2016 17:38

Я придумал макрос finally, который можно использовать почти как¹ ключевое слово finally в Java; он использует std::exception_ptr и другие, лямбда-функции и std::promise, поэтому для него требуется C++11 или выше; он также использует расширение составной оператор выражение GCC, которое также поддерживается clang.

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

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

#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();})) \
/***/

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

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

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

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

    finally {
        clean_it_all_up();
    }    
}

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


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

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

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

Да, это был просто быстрый взлом, но если программист знает, что делает, он тем не менее может быть полезен.

Fabio A. 01.08.2016 19:42

@MarkLakata, я обновил пост, добавив лучшую реализацию, которая поддерживает выдачу исключений и возвратов.

Fabio A. 02.08.2016 02:08

Выглядит хорошо. Вы можете избавиться от Caveat 2, просто вставив невозможный блок catch(xxx) {} в начало макроса finally, где xxx - это фиктивный тип только для того, чтобы иметь хотя бы один блок catch.

Mark Lakata 02.08.2016 02:31

@MarkLakata, я тоже думал об этом, но это сделало бы невозможным использование catch(...), не так ли?

Fabio A. 02.08.2016 02:37

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

Mark Lakata 02.08.2016 02:42

Gcc выплевывает finally.cpp:60:5: error: ‘...’ handler must be the last handler for its try block. Clang делает то же самое. Я считаю, что стандарт просто запрещает располагать блок catch(...) посередине.

Fabio A. 02.08.2016 02:44

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

Mark Lakata 02.08.2016 20:09

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

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
    }
);

Если хотите, можете обернуть эту идиому в макросы «попробуйте - наконец»:

// 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 :(

Теперь в C++ 11 доступен блок «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

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

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

PS

Если вам нужен блок наконец в вашем коде, то макросы охранники с прицелом или 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(); });

    ...

Как указано в других ответах, C++ может поддерживать функциональность, подобную finally. Реализация этой функциональности, которая, вероятно, наиболее близка к тому, чтобы быть частью стандартного языка, - это та, которая сопровождает Основные принципы C++, набор передовых методов использования C++, отредактированный Бьярном Стауструпом и Хербом Саттером. реализация finally является частью Библиотека поддержки рекомендаций (GSL). На всем протяжении Руководства рекомендуется использовать finally при работе с интерфейсами старого стиля, а также у него есть собственное руководство под названием Используйте объект final_action, чтобы выразить очистку, если подходящий дескриптор ресурса недоступен.

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

Пример использования реализации 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.
}

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

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

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

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

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

Другие вопросы по теме