我基本上是在寻找 fdopen() 的 C++ 版本。我对此进行了一些研究,这是其中似乎应该很容易的事情之一,但结果却非常复杂。我是否在这种信念中遗漏了一些东西(即它真的很容易)?如果没有,是否有一个好的图书馆可以处理这个问题?
编辑:将我的示例解决方案移至单独的答案。
我基本上是在寻找 fdopen() 的 C++ 版本。我对此进行了一些研究,这是其中似乎应该很容易的事情之一,但结果却非常复杂。我是否在这种信念中遗漏了一些东西(即它真的很容易)?如果没有,是否有一个好的图书馆可以处理这个问题?
编辑:将我的示例解决方案移至单独的答案。
从Éric Malenfant给出的答案:
AFAIK,在标准 C++ 中没有办法做到这一点。根据您的平台,您的标准库实现可能会提供(作为非标准扩展)一个 fstream 构造函数,将文件描述符作为输入。(libstdc++、IIRC 就是这种情况)或 FILE*。
基于上述观察和我在下面的研究,有两种变体的工作代码;一个用于 libstdc++,另一个用于 Microsoft Visual C++。
有非标准__gnu_cxx::stdio_filebuf
类模板继承std::basic_streambuf
并具有以下构造函数
stdio_filebuf (int __fd, std::ios_base::openmode __mode, size_t __size=static_cast< size_t >(BUFSIZ))
带描述此构造函数将文件流缓冲区与打开的 POSIX 文件描述符相关联。
我们通过 POSIX 句柄(第 1 行)创建它,然后将其作为 basic_streambuf 传递给 istream 的构造函数(第 2 行):
#include <ext/stdio_filebuf.h>
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
ofstream ofs("test.txt");
ofs << "Writing to a basic_ofstream object..." << endl;
ofs.close();
int posix_handle = fileno(::fopen("test.txt", "r"));
__gnu_cxx::stdio_filebuf<char> filebuf(posix_handle, std::ios::in); // 1
istream is(&filebuf); // 2
string line;
getline(is, line);
cout << "line: " << line << std::endl;
return 0;
}
曾经有非标准版本的 ifstream 构造函数采用 POSIX 文件描述符,但当前文档和代码都缺少它。ifstream 构造函数的另一个非标准版本采用 FILE*
explicit basic_ifstream(_Filet *_File)
: _Mybase(&_Filebuffer),
_Filebuffer(_File)
{ // construct with specified C stream
}
并且没有记录(我什至找不到任何旧文档)。我们调用它(第 1 行),参数是调用_fdopen以从 POSIX 文件句柄获取 C 流 FILE* 的结果。
#include <cstdio>
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
ofstream ofs("test.txt");
ofs << "Writing to a basic_ofstream object..." << endl;
ofs.close();
int posix_handle = ::_fileno(::fopen("test.txt", "r"));
ifstream ifs(::_fdopen(posix_handle, "r")); // 1
string line;
getline(ifs, line);
ifs.close();
cout << "line: " << line << endl;
return 0;
}
AFAIK,在标准 C++ 中没有办法做到这一点。根据您的平台,您的标准库实现可能会提供(作为非标准扩展)一个 fstream 构造函数,该构造函数采用文件描述符(libstdc++、IIRC 就是这种情况)或 aFILE*
作为输入。
另一种选择是使用boost::iostreams::file_descriptor设备,如果你想要一个 std::stream 接口,你可以将它包装在boost::iostreams::stream中。
您的编译器很有可能提供基于 FILE 的 fstream 构造函数,即使它是非标准的。例如:
FILE* f = fdopen(my_fd, "a");
std::fstream fstr(f);
fstr << "Greetings\n";
但据我所知,没有便携的方法可以做到这一点。
这个问题的部分原始(未说明)动机是能够使用安全创建的临时文件在程序之间或测试程序的两个部分之间传递数据,但是 tmpnam() 在 gcc 中引发警告,所以我想要改为使用 mkstemp()。这是我根据 Éric Malenfant 给出的答案编写的测试程序,但使用 mkstemp() 而不是 fdopen();这适用于我安装了 Boost 库的 Ubuntu 系统:
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <string>
#include <iostream>
#include <boost/filesystem.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
using boost::iostreams::stream;
using boost::iostreams::file_descriptor_sink;
using boost::filesystem::path;
using boost::filesystem::exists;
using boost::filesystem::status;
using boost::filesystem::remove;
int main(int argc, const char *argv[]) {
char tmpTemplate[13];
strncpy(tmpTemplate, "/tmp/XXXXXX", 13);
stream<file_descriptor_sink> tmp(mkstemp(tmpTemplate));
assert(tmp.is_open());
tmp << "Hello mkstemp!" << std::endl;
tmp.close();
path tmpPath(tmpTemplate);
if (exists(status(tmpPath))) {
std::cout << "Output is in " << tmpPath.file_string() << std::endl;
std::string cmd("cat ");
cmd += tmpPath.file_string();
system(cmd.c_str());
std::cout << "Removing " << tmpPath.file_string() << std::endl;
remove(tmpPath);
}
}
这实际上很容易。Nicolai M. Josuttisfdstream
与他的书The C++ Standard Library - A Tutorial and Reference一起发布。你可以在这里找到 184 行的实现。
我已经尝试过 Piotr Dobrogost 为 libstdc++ 提出的上述解决方案,发现它有一个痛苦的缺陷:由于 istream 缺少适当的移动构造函数,很难将新构造的 istream 对象从创建函数中取出. 它的另一个问题是它泄漏了一个 FILE 对象(甚至认为不是底层的 posix 文件描述符)。这是避免这些问题的替代解决方案:
#include <fstream>
#include <string>
#include <ext/stdio_filebuf.h>
#include <type_traits>
bool OpenFileForSequentialInput(ifstream& ifs, const string& fname)
{
ifs.open(fname.c_str(), ios::in);
if (! ifs.is_open()) {
return false;
}
using FilebufType = __gnu_cxx::stdio_filebuf<std::ifstream::char_type>;
static_assert( std::is_base_of<ifstream::__filebuf_type, FilebufType>::value &&
(sizeof(FilebufType) == sizeof(ifstream::__filebuf_type)),
"The filebuf type appears to have extra data members, the cast might be unsafe");
const int fd = static_cast<FilebufType*>(ifs.rdbuf())->fd();
assert(fd >= 0);
if (0 != posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL)) {
ifs.close();
return false;
}
return true;
}
对 posix_fadvise() 的调用演示了一个潜在的用途。另请注意,该示例使用static_assert并使用C++ 11,除了它应该在 C++ 03 模式下构建得很好。
我的理解是,为了保持代码的可移植性,C++ iostream 对象模型中的 FILE 指针或文件描述符没有关联。
也就是说,我看到几个地方提到了mds-utils或 boost 来帮助弥合这一差距。