Как построить поток c++ fstream из файлового дескриптора POSIX?


Я в основном ищу версию fdopen () на C++. Я провел небольшое исследование по этому вопросу, и это одна из тех вещей, которые кажутся легкими, но оказываются очень сложными. Я что-то упускаю в этой вере (т. е. это действительно легко)? Если нет, есть ли где-нибудь хорошая библиотека, чтобы справиться с этим?

изменить: переместил мой пример решения в отдельный ответ.

7 79

7 ответов:

из ответа, данного Эрик Malenfant:

AFAIK, нет никакого способа сделать это в стандарт C++. В зависимости от вашего платформа, ваша реализация стандартная библиотека может предложить (в качестве нестандартное расширение) a fstream конструктор, принимающий файловый дескриптор на вход. (Это касается libstdc++, IIRC) или файл*.

на основе вышеизложенных наблюдений и моих исследований ниже есть рабочий код в двух вариантах; один для 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) , а затем передаем его конструктору istream как basic_streambuf (строка 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;
}

Microsoft Visual C++

раньше были нестандартные версия конструктора ifstream, принимающего файловый дескриптор POSIX, но он отсутствует как из настоящее документы и из кода. Существует еще одна нестандартная версия конструктора ifstream, принимающая файл*

explicit basic_ifstream(_Filet *_File)
    : _Mybase(&_Filebuffer),
        _Filebuffer(_File)
    {   // construct with specified C stream
    }

и это не документировано (я даже не мог найти старую документацию, где она будет присутствовать). Мы называем это (строка 1) с помощью параметр, являющийся результатом вызова _fdopen чтобы получить файл потока C* из дескриптора файла POSIX.

#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 устройство, которое вы могли бы обернуть в boost:: iostreams:: stream если вы хотите иметь std:: stream интерфейс к нему.

есть хороший шанс, что ваш компилятор предлагает файловый конструктор fstream, даже если он нестандартный. Например:

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

но насколько я знаю, нет переносимый способ, чтобы сделать это.

часть исходной (неустановленной) мотивации этого вопроса заключается в том, чтобы иметь возможность передавать данные либо между программами, либо между двумя частями тестовой программы, используя безопасно созданный временный файл, но tmpnam () выдает предупреждение в gcc, поэтому я хотел использовать mkstemp () вместо этого. Вот тестовая программа, которую я написал на основе ответ Эрик Malenfant но используя на mkstemp() вместо fdopen(); это работает на моей системе Ubuntu с установленным библиотеки Boost:

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

я попробовал решение, предложенное выше для libstdc++ Петром Доброгостом, и обнаружил, что у него есть болезненный недостаток: из-за отсутствия правильного конструктора перемещения для istream очень сложно получить вновь построенный объект istream из функции создания. Еще одна проблема с ним заключается в том, что он пропускает файловый объект (даже если он не является базовым файловым дескриптором 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.

это на самом деле довольно легко. Николай Михайлович Хосуттис освободил fdstream в связи с его книгой стандартная библиотека C++ - учебник и справочник. Вы можете 184 линии реализации здесь.

Я понимаю, что в объектной модели c++ iostream нет связи с указателями файлов или дескрипторами файлов, чтобы сохранить переносимость кода.

тем не менее, я видел, что несколько мест относятся к mds-utils или повысить, чтобы помочь преодолеть этот разрыв.