Правильная замена отсутствующего слова «finally» в C ++ - PullRequest
11 голосов
/ 20 ноября 2008

Поскольку в C ++ нет 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...
    }
}

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

Ответы [ 6 ]

16 голосов
/ 20 ноября 2008

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

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

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

9 голосов
/ 20 ноября 2008

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

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

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

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

6 голосов
/ 20 ноября 2008

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

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

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

2 голосов
/ 24 ноября 2008

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

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...
  }
}
1 голос
/ 20 ноября 2008

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

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

0 голосов
/ 21 ноября 2008

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

...