¿Cómo leo un archivo completo en un std::string en C++?

Resuelto asked hace 15 años • 0 respuestas

¿Cómo leo un archivo en un archivo std::string, es decir, leo todo el archivo a la vez?

La persona que llama debe especificar el modo de texto o binario. La solución debe ser compatible con los estándares, portátil y eficiente. No debería copiar innecesariamente los datos de la cadena y debería evitar reasignaciones de memoria mientras lee la cadena.

Una forma de hacer esto sería establecer el tamaño del archivo, cambiar el std::stringtamaño y fread()al std::string's const_cast<char*>()'ed data(). Esto requiere que los std::stringdatos de sean contiguos, lo cual no es requerido por el estándar, pero parece ser el caso para todas las implementaciones conocidas. Lo que es peor, si el archivo se lee en modo texto, std::stringes posible que el tamaño de 'no sea igual al tamaño del archivo.

Se podrían construir soluciones portátiles, totalmente correctas y que cumplan con los estándares usando std::ifstream's rdbuf()en a std::ostringstreamy desde allí en a std::string. Sin embargo, esto podría copiar los datos de la cadena y/o reasignar memoria innecesariamente.

  • ¿Todas las implementaciones de bibliotecas estándar relevantes son lo suficientemente inteligentes como para evitar gastos generales innecesarios?
  • ¿Hay otra manera de hacerlo?
  • ¿Me perdí alguna función Boost oculta que ya proporciona la funcionalidad deseada?


void slurp(std::string& data, bool is_binary)
 avatar Sep 22 '08 23:09
Aceptado

Una forma es vaciar el búfer de flujo en un flujo de memoria separado y luego convertirlo a std::string(se omite el manejo de errores):

std::string slurp(std::ifstream& in) {
    std::ostringstream sstr;
    sstr << in.rdbuf();
    return sstr.str();
}

Esto es muy conciso. Sin embargo, como se indica en la pregunta, esto realiza una copia redundante y desafortunadamente no hay forma de eliminar esta copia.

Desafortunadamente, la única solución real que evita copias redundantes es realizar la lectura manualmente en un bucle. Dado que C++ ahora tiene cadenas contiguas garantizadas, se podría escribir lo siguiente (≥C++17, manejo de errores incluido):

auto read_file(std::string_view path) -> std::string {
    constexpr auto read_size = std::size_t(4096);
    auto stream = std::ifstream(path.data());
    stream.exceptions(std::ios_base::badbit);

    if (not stream) {
        throw std::ios_base::failure("file does not exist");
    }
    
    auto out = std::string();
    auto buf = std::string(read_size, '\0');
    while (stream.read(& buf[0], read_size)) {
        out.append(buf, 0, stream.gcount());
    }
    out.append(buf, 0, stream.gcount());
    return out;
}
Konrad Rudolph avatar Sep 22 '2008 17:09 Konrad Rudolph

La variante más corta:Live On Coliru

std::string str(std::istreambuf_iterator<char>{ifs}, {});

Requiere el encabezado <iterator>.

Hubo algunos informes de que este método es más lento que preasignar la cadena y usar std::istream::read. Sin embargo, en un compilador moderno con optimizaciones habilitadas, este ya no parece ser el caso, aunque el rendimiento relativo de varios métodos parece depender en gran medida del compilador.

Konrad Rudolph avatar Sep 22 '2008 17:09 Konrad Rudolph

Vea esta respuesta a una pregunta similar.

Para su comodidad, vuelvo a publicar la solución de CTT:

string readFile2(const string &fileName)
{
    ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);

    ifstream::pos_type fileSize = ifs.tellg();
    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(bytes.data(), fileSize);

    return string(bytes.data(), fileSize);
}

Esta solución dio como resultado tiempos de ejecución aproximadamente un 20% más rápidos que las otras respuestas presentadas aquí, cuando se toma el promedio de 100 ejecuciones del texto de Moby Dick (1,3 millones). Nada mal para una solución portátil de C++, me gustaría ver los resultados de mmap'ing el archivo;)

oz10 avatar Feb 08 '2009 03:02 oz10