提问人:Sascha 提问时间:11/20/2008 最后编辑:rlerallutSascha 更新时间:11/24/2008 访问量:6874
正确替换 C++ 中缺少的“finally”
Proper replacement for the missing 'finally' in 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...
}
}
所以我的问题是:有没有一种解决方案可以兼顾这两种优势?这样,您 a) 不必编写重复的代码,b) 可以访问清理代码中的局部变量,就像在上一个示例中一样,但没有这种代码膨胀。task
答:
我不认为有一种更干净的方法来实现你想要做的事情,但我认为你的例子中“最终方法”的主要问题是关注点的分离不当。
例如,函数 foo() 负责 Task 对象的一致性,这很少是一个好主意,Task 的方法本身应该负责将状态设置为合理的状态。
我确实意识到有时确实需要 finally,您的代码显然只是一个简单的示例来说明一个观点,但这些情况很少见。在极少数情况下,更人为的代码对我来说是可以接受的。
我想说的是,你很少需要最终构造,对于你这样做的少数情况,我会说不要浪费时间在构造一些更好的方法上。它只会鼓励你最终使用比你真正应该使用的更多......
您可以在类的函数中提取清理代码并使用 Loki 的 ScopeGuard,而不是定义 。struct Finally
Task
ScopeGuard guard = MakeGuard(&Task::cleanup, task);
另请参阅这篇 DrDobb 的文章和这篇关于 ScopeGuard 的更多信息的其他文章。
评论
正如其他人所说,“解决方案”是更好地分离关注点。 在你的例子中,为什么任务变量不能自行清理? 如果需要对它进行任何清理,那么它不应该是一个指针,而是一个 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。
但通常情况下,每种类型都应该知道如何自行清理。不是在引发异常时在范围内的每个对象之后(这是最终执行的,也是您尝试执行的),只是在它本身之后。如果每个对象都这样做,那么你根本不需要“在范围内的每个对象之后进行清理”这个包罗万象的功能。
评论
这是一种非常丑陋的方式:(你来自Java吗?
请阅读这篇文章:
C++ 是否支持“finally”块?(我一直听说的这个“RAII”是什么?
它解释了为什么 finally 是一个如此丑陋的概念,以及为什么 RIAA 更加优雅。
我通常使用更类似的东西:
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...
}
}
评论
RAII
finally