Como construir um fstream c ++ a partir de um descritor de arquivo POSIX?

93

Basicamente, estou procurando uma versão C ++ de fdopen (). Pesquisei um pouco sobre isso e é uma daquelas coisas que parece que deveria ser fácil, mas acaba sendo muito complicado. Estou perdendo algo nessa crença (ou seja, é realmente fácil)? Se não, existe uma boa biblioteca em algum lugar para lidar com isso?

EDIT: Mudei minha solução de exemplo para uma resposta separada.

BD em Rivenhill
fonte
@Kazark - mudou para uma resposta separada agora, obrigado.
BD em Rivenhill
Windows e Linux podem fazer mmappara o arquivo e expôs seu conteúdo como matriz de bytes.
truthadjustr

Respostas:

72

Da resposta de Éric Malenfant:

AFAIK, não há como fazer isso no C ++ padrão. Dependendo de sua plataforma, sua implementação da biblioteca padrão pode oferecer (como uma extensão não padrão) um construtor fstream tomando um descritor de arquivo como entrada. (Esse é o caso de libstdc ++, IIRC) ou um FILE *.

Com base nas observações acima e na minha pesquisa abaixo, há um código funcional em duas variantes; um para libstdc ++ e outro para Microsoft Visual C ++.


libstdc ++

Há um __gnu_cxx::stdio_filebufmodelo de classe não padrão que herda std::basic_streambufe tem o seguinte construtor

stdio_filebuf (int __fd, std::ios_base::openmode __mode, size_t __size=static_cast< size_t >(BUFSIZ)) 

com descrição Este construtor associa um buffer de fluxo de arquivo a um descritor de arquivo POSIX aberto.

Nós o criamos passando o identificador POSIX (linha 1) e então o passamos para o construtor do istream como basic_streambuf (linha 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 ++

Costumava haver uma versão não padrão do construtor do ifstream usando o descritor de arquivo POSIX, mas está faltando nos documentos atuais e no código. Há outra versão não padrão do construtor do ifstream pegando FILE *

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

e não está documentado (não consegui nem encontrar qualquer documentação antiga onde estivesse presente). Nós o chamamos (linha 1) com o parâmetro sendo o resultado da chamada _fdopen para obter o fluxo C FILE * do identificador de arquivo 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;
}
Piotr Dobrogost
fonte
2
Agora a resposta aceita por estar completa. Outros podem estar interessados ​​na minha solução usando boost, que foi movido para uma resposta separada.
BD em Rivenhill
1
Para linux: Se você olhar para ios_init.cc no gcc (a fonte que eu tenho é para a versão 4.1.1) std :: cout é inicializado inicializando um stdio_sync_filebuf <char> em torno de seu descritor de arquivo, então inicializando em ostream em torno de seu stdio_sync_filebuf < char>. Não posso afirmar que isso será estável.
Sparky
@Sparky Analisar a std::coutimplementação é uma boa ideia. Estou me perguntando qual é a diferença entre stdio_filebufe stdio_sync_filebuf?
Piotr Dobrogost
POSIX fds em MSVC são emulação. A API do Windows para operações de arquivo difere dos POSIX em muitos aspectos - diferentes nomes de funções e tipos de dados de parâmetros. O Windows usa internamente os chamados "identificadores" para identificar vários objetos de API do Windows e o tipo de API do Windows HANDLE é definido como void *, portanto em no mínimo, não caberá em "int" (que é de 32 bits) em plataformas de 64 bits. Portanto, para o Windows, você pode estar interessado em procurar um fluxo que permita trabalhar no arquivo HANDLE da API do Windows.
ivan.ukr
40

AFAIK, não há como fazer isso no C ++ padrão. Dependendo de sua plataforma, sua implementação da biblioteca padrão pode oferecer (como uma extensão não padrão) um construtor fstream tomando um descritor de arquivo (este é o caso de libstdc ++, IIRC) ou um FILE*como entrada.

Outra alternativa seria usar um dispositivo boost :: iostreams :: file_descriptor , que você poderia envolver em um boost :: iostreams :: stream se desejar ter uma interface std :: stream para ele.

Éric Malenfant
fonte
4
Considerando que esta é a única solução portátil, não entendo por que esta não é a resposta aceita ou bem avaliada.
Maarten
8

Há uma boa chance de seu compilador oferecer um construtor fstream baseado em FILE, embora não seja padrão. Por exemplo:

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

Mas, pelo que eu sei, não existe uma maneira portátil de fazer isso.

Darryl
fonte
2
Observe que g ++ (corretamente) não permite isso no modo c ++ 11
Mark K Cowan
8

Parte da motivação original (não declarada) desta questão é ter a capacidade de passar dados entre programas ou entre duas partes de um programa de teste usando um arquivo temporário criado com segurança, mas tmpnam () lança um aviso no gcc, então eu queria para usar mkstemp () ao invés. Aqui está um programa de teste que escrevi com base na resposta dada por Éric Malenfant, mas usando mkstemp () em vez de fdopen (); isso funciona no meu sistema Ubuntu com bibliotecas Boost instaladas:

#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);
  }
}
BD em Rivenhill
fonte
4

Eu tentei a solução proposta acima para libstdc ++ por Piotr Dobrogost, e descobri que ela tinha uma falha dolorosa: devido à falta de um construtor de movimento adequado para istream, é muito difícil tirar o objeto istream recém-construído da função de criação . Outro problema com ele é que ele vaza um objeto FILE (mesmo que não seja o descritor de arquivo posix subjacente). Esta é uma solução alternativa que evita esses problemas:

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

A chamada para posix_fadvise () demonstra um uso potencial. Observe também que o exemplo usa static_assert e usando que são C ++ 11, além de que ele deve construir muito bem em C ++ 03 modo.

YitzikC
fonte
O que você quer dizer com versão adequada do construtor de movimento ? Qual versão do gcc você usou? Talvez esta versão ainda não tivesse construtores de movimento implementados - consulte O construtor de movimento de ifsteam foi excluído implicitamente? ?
Piotr Dobrogost de
1
Este é um hack que depende de detalhes de implementação subjacentes. Espero que ninguém nunca use isso no código de produção.
davmac
-4

Meu entendimento é que não há associação com ponteiros de FILE ou descritores de arquivo no modelo de objeto iostream C ++ para manter o código portátil.

Dito isso, eu vi vários lugares se referindo aos mds-utils ou boost para ajudar a preencher essa lacuna.

plinto
fonte
9
FILE * é C padrão e, portanto, C ++, então não vejo como habilitar streams C ++ para trabalhar com streams C poderia prejudicar a portabilidade
Piotr Dobrogost