我如何读一个文件到一个std::字符串,即,读取整个文件一次?

文本或二进制模式应该由调用者指定。解决方案应该是符合标准的、可移植的和高效的。它不应该不必要地复制字符串的数据,并且应该避免在读取字符串时重新分配内存。

一种方法是统计文件大小,调整std::string和fread()到std::string的const_cast<char*>()'ed data()。这要求std::string的数据是连续的,这不是标准所要求的,但它似乎是所有已知实现的情况。更糟糕的是,如果以文本模式读取文件,std::string的大小可能不等于文件的大小。

一个完全正确的、符合标准的、可移植的解决方案可以使用std::ifstream的rdbuf()构造成std::ostringstream,再从那里构造成std::string。但是,这可能会复制字符串数据和/或不必要地重新分配内存。

是否所有相关的标准库实现都足够智能以避免所有不必要的开销? 还有别的办法吗? 我是否错过了一些已经提供所需功能的隐藏Boost函数?

void slurp(std::string& data, bool is_binary)

当前回答

最短的变体:Live On Coliru

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

它需要头文件<iterator>。

有一些报告说,这种方法比预先分配字符串和使用std::istream::read要慢。然而,在现代的编译器上,这种情况似乎不再存在,尽管各种方法的相对性能似乎高度依赖于编译器。

其他回答

如果你有c++ 17 (std::filesystem),也有这种方法(通过std::filesystem::file_size而不是seekg和tellg来获取文件大小):

#include <filesystem>
#include <fstream>
#include <string>

namespace fs = std::filesystem;

std::string readFile(fs::path path)
{
    // Open the stream to 'lock' the file.
    std::ifstream f(path, std::ios::in | std::ios::binary);

    // Obtain the size of the file.
    const auto sz = fs::file_size(path);

    // Create a buffer.
    std::string result(sz, '\0');

    // Read the whole file into the buffer.
    f.read(result.data(), sz);

    return result;
}

注意:如果你的标准库还不完全支持c++ 17,你可能需要使用<experimental/filesystem>和std::experimental::filesystem。你可能还需要用&result[0]替换result.data(),如果它不支持非const std::basic_string数据。

std::string get(std::string_view const& fn)
{
  struct filebuf: std::filebuf
  {
    using std::filebuf::egptr;
    using std::filebuf::gptr;

    using std::filebuf::gbump;
    using std::filebuf::underflow;
  };

  std::string r;

  if (filebuf fb; fb.open(fn.data(), std::ios::binary | std::ios::in))
  {
    r.reserve(fb.pubseekoff({}, std::ios::end));
    fb.pubseekpos({});

    while (filebuf::traits_type::eof() != fb.underflow())
    {
      auto const gptr(fb.gptr());
      auto const sz(fb.egptr() - gptr);

      fb.gbump(sz);
      r.append(gptr, sz);
    }
  }

  return r;
}

最短的变体:Live On Coliru

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

它需要头文件<iterator>。

有一些报告说,这种方法比预先分配字符串和使用std::istream::read要慢。然而,在现代的编译器上,这种情况似乎不再存在,尽管各种方法的相对性能似乎高度依赖于编译器。

请看类似问题的答案。

为了方便大家,我转发了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);
}

当对《白鲸记》(Moby Dick, 1.3M)的文本进行平均100次运行时,该解决方案比本文给出的其他答案的执行时间快了约20%。对于一个可移植的c++解决方案来说还不错,我想看看mmap'ing文件的结果;)

你可以使用我开发的第一个c++库来做到这一点:

#include "rst/files/file_utils.h"

std::filesystem::path path = ...;  // Path to a file.
rst::StatusOr<std::string> content = rst::ReadFile(path);
if (content.err()) {
  // Handle error.
}

std::cout << *content << ", " << content->size() << std::endl;