Правильная замена отсутствующего "наконец" на С++

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

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...
    }
}

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

Ответ 1

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

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

Смотрите также эту статью DrDobb и эту другую статью для получения дополнительных сведений о ScopeGuards.

Ответ 2

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

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

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

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

Ответ 4

Я обычно использую нечто более похожее:

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...
  }
}

Ответ 5

Как говорили другие, "решение" - лучшее разделение проблем. В вашем случае, почему переменная задачи не может заботиться о ее очистке? Если на нем нужно выполнить какую-либо очистку, то это не должно быть указателем, а объектом 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 будет удалять указатель по умолчанию, но вместо этого вы можете указать пользовательские функции отсрочки, которые могут выполнять произвольную очистку takss. Для RAII на указателях обычно это вы захотите.

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

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

Ответ 6

Я пришел на С++ из Delphi, поэтому я знаю, о чем говорю. Я ненавижу наконец!!! Это уродливое. Я действительно не думаю, что С++ отсутствует.