11

由于finallyC++中没有,因此如果您希望代码具有异常安全性,则必须改用 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在上一个示例中一样,但没有这样的代码膨胀。

4

5 回答 5

16

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

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

有关 ScopeGuard 的更多信息,另请参阅此DrDobb 的文章和此其他文章

于 2008-11-20T11:57:24.257 回答
9

我认为没有更清洁的方法可以实现您想要做的事情,但我认为您示例中“最终方法”的主要问题是关注点分离不当。

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

我确实意识到有时确实需要 finally,而您的代码显然只是一个简单的示例来说明一个观点,但这种情况很少见。在极少数情况下,我可以接受更多人为的代码。

我想说的是,您应该很少需要 finally 构造,并且对于您这样做的少数情况,我想说不要浪费时间构建更好的方法。它只会鼓励你最终使用比你真正应该使用的更多......

于 2008-11-20T11:35:42.287 回答
6

这是一种丑陋的做法:(你是从 Java 来的吗?)

请阅读这篇文章:
C++ 是否支持“finally”块?(我一直听到的这个“RAII”是什么?)

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

于 2008-11-20T17:24:35.250 回答
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...
  }
}
于 2008-11-24T11:39:15.213 回答
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 的原始指针。

但通常,每种类型都应该知道如何清理自己。不是在抛出异常时范围内的每个对象之后(这是最终所做的,以及您试图做的),只是在其自身之后。如果每个对象都这样做,那么您根本不需要大的包罗万象的“在范围内的每个对象之后清理”功能。

于 2008-11-20T16:17:38.193 回答