Правильная замена отсутствующего 'finally' в C++

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

void foo() {
    struct Finally {
        ~Finally() { /* cleanup code */ }
    } finalizer();
    // ...code that might throw an exception...
}

Это большое преимущество перед простым решением, потому что вам не нужно писать код очистки 2 раза:

try {
    // ...code that might throw an exception...
    // cleanup code (no exception)
} catch (...) {
    // cleanup code (exception)
    throw;
}

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

void foo() {
    Task* task;
    while (task = nextTask()) {
        task->status = running;
        struct Finally {
            Task* task;
            Finally(Task* task) : task(task) {}
            ~Finally() { task->status = idle; }
        } finalizer(task);
        // ...code that might throw an exception...
    }
}

Итак, мой вопрос: Есть решение, которое сочетает в себе оба преимущества? Так что вам а) не нужно писать повторяющийся код и б) иметь доступ к локальным переменным в коде очистки, как task в последнем примере, но без такого раздувания кода.

Это уродливо. Вы должны создать RunObject или что-то в этом роде !!!

Martin York 20.11.2008 20:26

+1 за то, что спрашиваю об этом, потому что люди, не знакомые с RAII, часто думают, что finally - это хорошо ...

Piotr Dobrogost 14.06.2010 00:50

«вам не нужно писать код очистки 2 раза». Зачем вам вообще писать код дважды по какой-либо причине? Вот для чего нужны подпрограммы = P Нейтрально в вопросе RAII vs finally, но написание кода дважды - отвлекающий маневр: вы можете создать процедуру очистки, передать ей задачу и вызвать ее в двух местах. Дублирование кода не требуется. В вашем простом примере это логически является частью деструктора задачи. Но если есть какая-то причина, по которой вы хотите очистить foo, создайте там локальную процедуру очистки.

ToolmakerSteve 21.11.2013 06:54
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
11
3
6 796
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

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

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

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

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

Ответ принят как подходящий

Вместо определения struct Finally вы можете извлечь код очистки в функции класса Task и использовать ScopeGuard Локи.

ScopeGuard guard = MakeGuard(&Task::cleanup, task);

См. Также этот Статья DrDobb и этот другая статья для получения дополнительной информации о ScopeGuards.

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

Jacek Sieka 18.07.2012 16:50

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

void foo() {
//    Task* task;
ScopedTask task; // Some type which internally stores a Task*, but also contains a destructor for RAII cleanup
    while (task = nextTask()) {
        task->status = running;
        // ...code that might throw an exception...
    }
}

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

Проблема не в отсутствии ключевого слова finally, а в том, что вы используете необработанные указатели, которые не могут реализовать RAII.

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

удалил мой комментарий, так как мой пример сокета был meh :) Я думаю, что согласен, есть несколько случаев, когда, наконец, можно использовать. но все равно было бы неплохо иметь / смоделировать :)

Johannes Schaub - litb 20.11.2008 19:23

Это такой уродливый способ сделать это: (вы пришли с Java?)

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

Это объясняет, почему, наконец, такая уродливая концепция и почему RIAA намного элегантнее.

Я перешел на C++ из Delphi, поэтому я знаю, о чем говорю. Я НЕНАВИЖУ наконец !!! Это уродливый. Я действительно не думаю, что С ++ наконец-то пропал.

Обычно я использую что-то вроде этого:

class Runner {
private:
  Task & task;
  State oldstate;
public:
  Runner (Task &t, State newstate) : task(t), oldstate(t.status); 
  {
    task.status = newstate;
  };

  ~Runner() 
  {
    task.status = oldstate;
  };
};

void foo() 
{
  Task* task;
  while (task = nextTask())
  {
    Runner r(*task, running);
            // ...code that might throw an exception...
  }
}

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