如何从POSIX文件描述符构造c ++ fstream?


93

我基本上是在寻找fdopen()的C ++版本。我对此进行了一些研究,这似乎是一件容易的事,但事实却很复杂。我是否出于这种信念而错过了某些东西(即确实很容易)?如果不是,是否有一个好的图书馆可以解决这个问题?

编辑:将我的示例解决方案移到一个单独的答案。


@Kazark-现在移到一个单独的答案,谢谢。
BD,里文希尔(Rivenhill),

Windows和Linux可以mmap处理该文件,并将其内容公开为字节数组。
trueadjustr

Answers:


72

根据ÉricMalenfant的回答:

AFAIK,在标准C ++中无法做到这一点。根据您的平台,您对标准库的实现可能会提供fstream构造函数(作为非标准扩展名),以文件描述符作为输入。(对于libstdc ++,IIRC就是这种情况)或FILE *。

根据上面的观察和下面的研究,工作代码分为两种:一个用于libstdc ++,另一个用于Microsoft Visual C ++。


libstdc ++

有一个非标准的__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(第2行)传递给istream的构造函数:

#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;
}

Microsoft Visual C ++

曾经是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;
}

2
现在,由于完整性,可以接受的答案。其他人可能对我使用boost的解决方案感兴趣,该解决方案已移至单独的答案。
2013年

1
对于linux:如果您在gcc中查看ios_init.cc(我的源代码是4.1.1版),则通过在文件描述符周围初始化stdio_sync_filebuf <char>来初始化std :: cout,然后在stdio_sync_filebuf <的ostream上进行初始化char>。我不能说这将是稳定的。
Sparky

@Sparky研究std::cout实现是一个好主意。我想知道stdio_filebuf和之间有什么区别stdio_sync_filebuf
Piotr Dobrogost '16

MSVC中的POSIX fds是仿真的。用于文件操作的Windows API在很多方面与POSIX有所不同-不同的函数名称和参数的数据类型。Windows内部使用所谓的“句柄”来标识各种Windows API对象,并且Windows API类型的HANDLE被定义为void *,因此最小它在64位平台上不适合“ int”(32位)。因此,对于Windows,您可能有兴趣寻找允许在Windows API文件HANDLE上工作的流。
ivan.ukr

40

AFAIK,在标准C ++中无法做到这一点。根据您的平台,标准库的实现可能会提供(作为非标准扩展名)fstream构造函数,该构造函数采用文件描述符(对于libstdc ++,IIRC就是这种情况)或FILE*作为输入。

另一种选择是使用boost :: iostreams :: file_descriptor设备,如果您希望具有std :: stream接口,可以将其包装在boost :: iostreams :: stream中


4
考虑到这是唯一的便携式解决方案,我不明白为什么这不是公认的或最高评价的答案。
马滕

8

即使它是非标准的,您的编译器也很有可能提供基于FILE的fstream构造函数。例如:

FILE* f = fdopen(my_fd, "a");
std::fstream fstr(f);
fstr << "Greetings\n";

但是据我所知,没有便携式的方法可以做到这一点。


2
请注意,g ++(正确)在c ++ 11模式下不允许这样做
Mark K Cowan 2014年

8

这个问题的原始(未声明)动机的一部分是要能够使用安全创建的临时文件在程序之间或测试程序的两个部分之间传递数据,但tmpnam()在gcc中会发出警告,因此我想使用mkstemp()代替。这是我根据ÉricMalenfant给出的答案编写的一个测试程序,但是使用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);
  }
}


4

我尝试了上面由Piotr Dobrogost为libstdc ++提出的解决方案,发现它有一个令人痛苦的缺陷:由于缺乏适用于istream的move构造函数,因此很难从创建函数中获取新构造的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模式下应该可以正常构建。


正确版本的move构造函数是什么意思?您使用的是什么版本的gcc?也许此版本尚未实现move构造函数-请参见ifsteam的move构造函数是否隐式删除?
Piotr Dobrogost '16

1
这是一个依赖于底层实现细节的黑客。我希望没人能在生产代码中使用它。
davmac '16

-4

我的理解是,为了保持代码的可移植性,在C ++ iostream对象模型中没有与FILE指针或文件描述符关联。

就是说,我看到有几个地方提到了mds-utils或boost来帮助弥合这一差距。


9
FILE *是标准C,因此是C ++,因此我看不到启用C ++流与C流一起使用会如何损害可移植性
Piotr Dobrogost 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.