¿Cómo construir un fstream de c ++ a partir de un descriptor de archivo POSIX?

93

Básicamente estoy buscando una versión C ++ de fdopen (). Investigué un poco sobre esto y es una de esas cosas que parece que debería ser fácil, pero resulta ser muy complicado. ¿Me estoy perdiendo algo en esta creencia (es decir, realmente es fácil)? Si no es así, ¿hay una buena biblioteca en algún lugar para manejar esto?

EDITAR: Moví mi solución de ejemplo a una respuesta separada.

BD en Rivenhill
fuente
@Kazark - movido a una respuesta separada ahora, gracias.
BD en Rivenhill
Windows y Linux pueden hacer mmapcon el archivo y exponer su contenido como matriz de bytes.
eigenfield

Respuestas:

72

De la respuesta dada por Éric Malenfant:

AFAIK, no hay forma de hacer esto en C ++ estándar. Dependiendo de su plataforma, su implementación de la biblioteca estándar puede ofrecer (como una extensión no estándar) un constructor fstream que toma un descriptor de archivo como entrada. (Este es el caso de libstdc ++, IIRC) o un FILE *.

Según las observaciones anteriores y mi investigación a continuación, hay un código de trabajo en dos variantes; uno para libstdc ++ y otro para Microsoft Visual C ++.


libstdc ++

Hay una __gnu_cxx::stdio_filebufplantilla de clase no estándar que hereda std::basic_streambufy tiene el siguiente constructor

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

con descripción Este constructor asocia un búfer de flujo de archivos con un descriptor de archivo POSIX abierto.

Lo creamos pasando el identificador POSIX (línea 1) y luego lo pasamos al constructor de istream como basic_streambuf (línea 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 ++

Solía ​​haber una versión no estándar del constructor de ifstream que tomaba el descriptor de archivo POSIX, pero falta tanto en los documentos actuales como en el código. Hay otra versión no estándar del constructor de ifstream que toma FILE *

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

y no está documentado (ni siquiera pude encontrar ninguna documentación antigua donde estaría presente). Lo llamamos (línea 1) y el parámetro es el resultado de llamar a _fdopen para obtener la secuencia C FILE * del identificador del archivo 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
fuente
2
Ahora la respuesta aceptada debido a la completitud. Otros pueden estar interesados ​​en mi solución usando boost, que se ha movido a una respuesta separada.
BD en Rivenhill
1
Para linux: si mira ios_init.cc en gcc (la fuente que tengo es para la versión 4.1.1) std :: cout se inicializa inicializando un stdio_sync_filebuf <char> alrededor de su descriptor de archivo, luego inicializando en ostream alrededor de su stdio_sync_filebuf < char>. Sin embargo, no puedo afirmar que esto vaya a ser estable.
Sparky
@Sparky Estudiar la std::coutimplementación es una buena idea. Me pregunto cuál es la diferencia entre stdio_filebufy stdio_sync_filebuf.
Piotr Dobrogost
Los fds de POSIX en MSVC son emulación. La API de Windows para operaciones con archivos difiere de las de POSIX en muchos aspectos: diferentes nombres de funciones y tipos de datos de parámetros. Windows utiliza internamente los llamados "identificadores" para identificar varios objetos de la API de Windows, y el tipo de API de Windows HANDLE se define como void *, por lo que en como mínimo, no cabe en "int" (que es de 32 bits) en plataformas de 64 bits. Entonces, para Windows, puede estar interesado en buscar una secuencia que permita trabajar con el archivo HANDLE de la API de Windows.
ivan.ukr
40

AFAIK, no hay forma de hacer esto en C ++ estándar. Dependiendo de su plataforma, su implementación de la biblioteca estándar puede ofrecer (como una extensión no estándar) un constructor fstream tomando un descriptor de archivo (este es el caso de libstdc ++, IIRC) o FILE*como entrada.

Otra alternativa sería usar un dispositivo boost :: iostreams :: file_descriptor , que podría envolver en un boost :: iostreams :: stream si desea tener una interfaz std :: stream.

Éric Malenfant
fuente
4
Teniendo en cuenta que esta es la única solución portátil, no entiendo por qué esta no es la respuesta aceptada o mejor calificada.
Maarten
8

Es muy probable que su compilador ofrezca un constructor fstream basado en FILE, aunque no sea estándar. Por ejemplo:

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

Pero hasta donde yo sé, no hay una forma portátil de hacer esto.

Darryl
fuente
2
Tenga en cuenta que g ++ (correctamente) no permitirá esto en el modo c ++ 11
Mark K Cowan
8

Parte de la motivación original (no declarada) de esta pregunta es tener la capacidad de pasar datos entre programas o entre dos partes de un programa de prueba utilizando un archivo temporal creado de forma segura, pero tmpnam () arroja una advertencia en gcc, así que quería para usar mkstemp () en su lugar. Aquí hay un programa de prueba que escribí basado en la respuesta dada por Éric Malenfant pero usando mkstemp () en lugar de fdopen (); esto funciona en mi sistema Ubuntu con las 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 en Rivenhill
fuente
4

Probé la solución propuesta anteriormente para libstdc ++ por Piotr Dobrogost, y descubrí que tenía un defecto doloroso: debido a la falta de un constructor de movimiento adecuado para istream, es muy difícil sacar el objeto istream recién construido de la función de creación . Otro problema con él es que filtra un objeto FILE (incluso aunque no sea el descriptor de archivo posix subyacente). Aquí hay una solución alternativa que evita estos 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;
}

La llamada a posix_fadvise () demuestra un uso potencial. También tenga en cuenta que el ejemplo usa static_assert y usa C ++ 11, aparte de eso, debería compilarse bien en el modo C ++ 03.

YitzikC
fuente
¿Qué quiere decir con la versión adecuada de move constructor ? ¿Qué versión de gcc usaste? Quizás esta versión aún no tenía implementados los constructores de movimientos; consulte ¿Se ha eliminado implícitamente el constructor de movimientos de ifsteam? ?
Piotr Dobrogost
1
Este es un truco que depende de los detalles de implementación subyacentes. Espero que nadie use esto en el código de producción.
davmac
-4

Tengo entendido que no hay asociación con punteros de ARCHIVO o descriptores de archivo en el modelo de objetos de iostream de C ++ para mantener el código portátil.

Dicho esto, vi en varios lugares referirse a mds-utils o boost para ayudar a cerrar esa brecha.

pedestal
fuente
9
ARCHIVO * es C estándar y, por lo tanto, C ++, por lo que no veo cómo permitir que los flujos de C ++ funcionen con los flujos de C podría dañar la portabilidad
Piotr Dobrogost