Як побудувати c ++ fstream з дескриптора файлу POSIX?


93

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

РЕДАКТУВАННЯ: Моє прикладне рішення перемістилося на окрему відповідь.


@Kazark - зараз перейшов до окремої відповіді, дякую.
BD у Rivenhill

Windows та Linux можуть робити mmapфайл і викривати його вміст у вигляді байтового масиву.
truthadjustr

Відповіді:


72

З відповіді Еріка Маленфанта:

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), а потім передаємо його до конструктора 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, що приймає FILE *

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

і це не задокументовано (я навіть не міг знайти жодної старої документації, де вона була б присутня). Ми називаємо це (рядок 1), параметр є результатом виклику _fdopen для отримання потоку C FILE * з обробки файлів 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;
}

2
Тепер прийнята відповідь через повноту. Інші можуть бути зацікавлені в моєму рішенні за допомогою boost, який було переміщено до окремої відповіді.
BD у Rivenhill

1
Для Linux: Якщо ви подивитесь на ios_init.cc у gcc (джерело, яке я маю для версії 4.1.1) std :: cout ініціалізується шляхом ініціалізації stdio_sync_filebuf <char> навколо дескриптора файлу, а потім ініціалізації в потоці навколо вашого stdio_sync_filebuf < char>. Я не можу стверджувати, що це буде стабільним, хоча.
Спаркі

@Sparky Дивлячись на std::coutреалізацію - це гарна ідея. Мені цікаво, в чому різниця між stdio_filebufі stdio_sync_filebuf?
Пьотр Доброгост

POSIX FDS в MSVC є емуляцією. API Windows для файлових операцій багато в чому відрізняється від POSIX - різні назви функцій і типи даних параметрів. Windows внутрішньо використовує так звані "ручки" для ідентифікації різних об'єктів API Windows, а тип Windows API HANDLE визначається як недійсний *, так що як мінімум, він не впишеться в "int" (що є 32-бітним) на 64-бітних платформах. Тож для Windows вам може бути цікаво шукати потік, який дозволяє працювати над файлом API API HANDLE.
ivan.ukr

40

AFAIK, у стандартних C ++ немає можливості зробити це. Залежно від вашої платформи, ваша реалізація стандартної бібліотеки може запропонувати (як нестандартне розширення) конструктор fstream, який приймає дескриптор файлу (це стосується libstdc ++, IIRC) або а FILE*.

Іншою альтернативою може бути використання пристрою boost :: iostreams :: file_descriptor , який ви можете обернути в boost :: iostreams :: stream, якщо хочете мати до нього std :: 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

8

Частина оригінальної (невстановленої) мотивації цього питання полягає у можливості передачі даних або між програмами, або між двома частинами тестової програми, використовуючи безпечно створений тимчасовий файл, але tmpnam () кидає попередження у gcc, тому я хотів використовувати натомість mkstemp (). Ось тестова програма, яку я написав на основі відповіді Еріка Маленфанта, але використовуючи 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);
  }
}


4

Я спробував рішення, запропоноване вище для libstdc ++ від Piotr Dobrogost, і виявив, що він мав болісний недолік: Через відсутність належного конструктора переміщення для 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.


Що ви маєте на увазі під правильною версією конструктора ходу ? Яку версію gcc ви використовували? Можливо, у цій версії ще не були реалізовані конструктори переміщення - див. Чи неявно видалено конструктор переміщення ifsteam? ?
Пьотр Доброгост

1
Це злом, який залежить від основних деталей реалізації. Я сподіваюся, що ніхто ніколи не використовує це у виробничому коді.
davmac

-4

Я розумію, що в об'єктній моделі іонів C ++ об'єктів не існує асоціації з вказівниками FILE або дескрипторами файлів, щоб зберегти код переносним.

Однак, я бачив, що в декількох місцях посилаються на mds-утиліти або прискорення, щоб допомогти усунути цей пробіл.


9
FILE * - це стандартний C, а отже, C ++, тому я не бачу, як включення потоків C ++ у роботу з потоками C може зашкодити переносимості
Piotr Dobrogost
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.