我正在创建一种文件格式,我想在文件中写一条显式消息,表明编写器已完成运行。我过去遇到了生成文件的问题,生成程序崩溃,文件被截断而没有我意识到,因为没有明确的标记,没有办法读取程序来检测文件是不完整的。
所以我有一个用于编写这些文件的类。现在通常如果你有一个“打开”操作和一个“关闭”操作你想使用RAII,那么我会把代码写在析构函数中的文件结束标记。这样用户就不会忘记。但是在写入没有完成因为抛出异常的情况下,析构函数仍然会被运行 - 在这种情况下我们不想编写消息,因此读者会知道文件是不完整的。
这似乎是在任何时候进行“提交”操作时都会发生的事情。你想要RAII,所以你不能忘记提交,但你也不想在发生异常时提交。这里的诱惑是使用std :: uncaught_exceptions,但我认为这是代码味道。
通常的解决方案是什么?只要求人们记得吗?我担心每当有人试图使用我的API时,这将是一个绊脚石。
答案 0 :(得分:0)
解决此问题的一种方法是实现一个简单的框架系统,您可以在其中定义仅在写入结束时完全填充的标头。包含SHA256哈希以使标头可用于验证文件的内容。这通常比在文件末尾读取字节要方便得多。
在实现方面,你写出一个标题,其中一些字段故意归零,写入有效负载的内容,同时通过散列方法提供数据,然后寻找标题并用最终值重写。该文件以明显无效的状态开始,如果一切都完成,则结束有效 。
你可以在处理实现细节的流句柄中包装所有这些,因为调用代码只关心它只是打开一个常规文件。如果标题不完整或无效,您的阅读版本将抛出异常。
答案 1 :(得分:0)
对于您的示例,如果您在完成写入文件时添加了类的用户调用的commit
方法,RAII似乎可以正常工作。
class MyFileFormat {
public:
MyFileFormat() : committed_(false) {}
~MyFileFormat() {
if (committed_) {
// write the completion footer (I hope this doesn't throw!)
}
// close the underlying stream...
}
bool open(const std::string& path) {
committed_ = false;
// open the underlying stream...
}
bool commit() {
committed_ = true;
}
};
用户有责任在完成后致电commit
,但至少可以确保资源已关闭。
有关此类案例的更一般模式,请查看ScopeGuards。
ScopeGuards会将清理责任移出您的类,并且可以在ScopeGuard超出范围并在被明确解除之前销毁时用于指定任意“清理”回调。在您的情况下,您可以扩展想法以支持故障清除(例如,关闭文件句柄)和成功清理(例如,写入完成页脚和关闭文件句柄)的回调。
答案 2 :(得分:0)
我通过写一个临时文件来处理这种情况。即使您要附加到文件,也要附加到该文件的临时副本。
在析构函数中,您可以检查std::uncaught_exception()以确定是否应将临时文件移动到其预期位置。