簡體   English   中英

如何從 POSIX 文件描述符構造 c++ fstream?

[英]How to construct a c++ fstream from a POSIX file descriptor?

我基本上是在尋找 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)) 

with description此構造函數將文件流緩沖區與打開的 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;
}

微軟 Visual C++

曾經有采用 POSIX 文件描述符的 ifstream 構造函數的非標准版本,但它在當前文檔和代碼中都沒有。 還有另一個非標准版本的 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 就是這種情況)或FILE*作為輸入。

另一種選擇是使用boost::iostreams::file_descriptor設備,如果你想有一個 std::stream 接口,你可以將它包裝在boost::iostreams::stream 中

這個問題的部分原始(未說明)動機是能夠使用安全創建的臨時文件在程序之間或測試程序的兩個部分之間傳遞數據,但 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);
  }
}

您的編譯器很有可能提供基於 FILE 的 fstream 構造函數,即使它是非標准的。 例如:

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

但據我所知,沒有可移植的方法來做到這一點。

這實際上很容易。 Nicolai M. Josuttis 連同他的書The C++ Standard Library - A Tutorial and Reference一起發布了fdstream 您可以在此處找到 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 來幫助彌合這一差距。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM