正确替换C ++中缺少的'finally'

时间:2008-11-20 11:10:37

标签: c++ exception finally c++-faq

由于C ++ you have to use the RAII设计模式中没有finally,所以如果您希望代码是异常安全的。一种方法是使用像这样的本地类的析构函数:

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

与直接解决方案相比,这是一个很大的优势,因为您不必两次编写清理代码:

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,但没有这样的代码膨胀。

6 个答案:

答案 0 :(得分:16)

您可以在类struct Finally的函数中提取清理代码,而不是定义Task,而是使用Loki的ScopeGuard

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

有关ScopeGuards的更多信息,另请参阅此DrDobb's article和此other article

答案 1 :(得分:9)

我认为没有更简洁的方法来实现您的目标,但我认为您的示例中“最终方法”的主要问题是不正确的separation of concerns

E.g。函数foo()负责Task对象的一致性,这很少是一个好主意,Task本身的方法应该负责将状态设置为合理的。

我确实有时候确实需要最终,而你的代码显然只是一个显示要点的简单例子,但这些情况很少见。在极少数情况下,我可以接受一些更人为的代码。

我想说的是,你应该很少需要最终的结构,而对于你做的少数情况,我会说不要浪费时间去构建一些更好的方法。它只会鼓励你最终使用比你真正应用的更多...

答案 2 :(得分:6)

这是一种如此丑陋的方式:(你来自Java吗?)

请阅读这篇文章:
Does C++ support 'finally' blocks? (And what's this 'RAII' I keep hearing about?)

它解释了为什么最终是一个如此丑陋的概念以及为什么RIAA更加优雅。

答案 3 :(得分:2)

我通常使用更像这样的东西:

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

答案 4 :(得分:1)

正如其他人所说,“解决方案”是更好地分离关注点。 在您的情况下,为什么任务变量不能自行清理? 如果需要对它进行任何清理,那么它不应该是一个指针,而是一个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。

但通常,每种类型都应该知道如何清理自己。不是在抛出异常后的每个对象之后(这是最终做的,以及你想要做的事情),就在它自己之后。如果每个对象都这样做,那么你根本就不需要大的“全部清理范围内的每个对象”功能。

答案 5 :(得分:-2)

我从Delphi来到C ++,所以我知道我在说什么。我终于恨了!!!这是丑陋的。我真的不认为C ++最终会丢失。