我想确保 ofstream 已写入磁盘设备。执行此操作的可移植方式(在 POSIX 系统上可移植)是什么?
如果我在
只读追加模式下单独
open
fsync
,这是否可以解决问题?像这样:
ofstream out(filename);
/* ...
write content into out
...
*/
out.close();
int fd = open(filename, O_APPEND);
fsync(fd);
close(fd);
如果您能够使用 Boost,请尝试基于 file_descriptor_sink 的流,例如:
#include <boost/filesystem.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <unistd.h>
boost::filesystem::path filePath("some-file");
boost::iostreams::stream<boost::iostreams::file_descriptor_sink> file(filePath);
// Write some stuff to file.
// Ensure the buffer's written to the OS ...
file.flush();
// Tell the OS to sync it with the real device.
//
::fdatasync(file->handle());
不幸的是,纵观标准,
basic_filebuf
或任何basic_[io]?fstream
类模板都没有提供任何内容来允许您提取底层操作系统文件描述符(就像fileno()
对C stdio I/O所做的那样) .
也没有
open()
方法或构造函数将此类文件描述符作为参数(这将允许您使用不同的机制打开文件并记录文件句柄)。
有
basic_ostream::flush()
,但是我怀疑这实际上并没有调用fsync()
——我希望像stdio中的fflush()
一样,它只确保刷新用户空间运行时库缓冲区,这意味着操作系统可能仍在缓冲数据。
简而言之,似乎没有办法可移植地做到这一点。 :(
该怎么办? 我的建议是子类化
basic_filebuf<C, T>
:
template <typename charT, typename traits = std::char_traits<charT> >
class my_basic_filebuf : public basic_filebuf<charT, traits> {
....
public:
int fileno() { ... }
....
};
typedef my_basic_filebuf<char> my_filebuf;
要使用它,您可以使用默认构造函数构造一个
ofstream
,然后使用rdbuf()
分配新缓冲区:
my_filebuf buf;
buf.open("somefile.txt");
ofstream ofs;
ofs.rdbuf(&buf);
ofs << "Writing to somefile.txt..." << endl;
int fd = static_cast<my_filebuf*>(ofs.rdbuf())->fileno();
当然你也可以从
basic_ostream
派生一个新类,使打开文件和检索文件描述符的过程更加方便。
std::filebuf
内部可能有一个文件描述符,但要获取它需要可怕的特定于实现的 hack。
这是 libstdc++ 的一个如此可怕的 hack。
#include <fstream>
#include <unistd.h>
int GetFileDescriptor(std::filebuf& filebuf)
{
class my_filebuf : public std::filebuf
{
public:
int handle() { return _M_file.fd(); }
};
return static_cast<my_filebuf&>(filebuf).handle();
}
int main()
{
std::ofstream out("test");
out << "Hello, world!";
out.flush();
fsync(GetFileDescriptor(*out.rdbuf()));
}
sync()
比打电话给
fsync(...)
更好,除非有人能告诉我为什么我应该害怕。对于性能要求不高的用例来说,额外的成本并不高。并且
fsync(...)
还要求您
fsync(...)
父目录(及其父目录,如果您在创建文件之前调用了
std::filesystem::create_directories()
),以便安全、完整地将文件提交到存储。与其让自己负担代码来执行
std::filesystem::directories
的同步版本,并捕获打开文件的路径以便您可以
fsync(...)
父目录,不如使用
sync()
来代替,这似乎要轻松得多。我的实现:
ofstream_synced.hpp
#pragma once
#include <fstream>
namespace pipedal
{
void filesystem_sync(); // for when you're doing a batch of files.
class ofstream_synced : public std::ofstream
{
public:
ofstream_synced() {}
explicit ofstream_synced(
const std::string &filename, ios_base::openmode mode =
ios_base::out
)
: std::ofstream(filename, mode)
{
}
explicit ofstream_synced(
const char *filename,
ios_base::openmode mode = ios_base::out
)
: std::ofstream(filename, mode)
{
}
void close();
~ofstream_synced();
};
}
ofstream_synced.cpp
#include "ofstream_synced.hpp"
#include "unistd.h"
using namespace pipedal;
void pipedal::filesystem_sync()
{
::sync();
}
void ofstream_synced:close() {
if (is_open())
{
super::close();
::sync();
}
}
ofstream_synced::~ofstream_synced()
{
ofstream_synced::close();
}
.cpp 文件的唯一目的是将 unistd.h
声明保留在全局命名空间之外。如果您不关心的话,您可以内联 .cpp 函数。