我如何读一个文件到一个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)

当前回答

你可以使用我开发的第一个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;

其他回答

最短的变体:Live On Coliru

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

它需要头文件<iterator>。

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

永远不要写入std::string的const char *缓冲区。从来没有!这样做是一个巨大的错误。

在std::string中为整个字符串保留()空间,将合理大小的文件中的块读入缓冲区,然后追加()它。数据块的大小取决于输入文件的大小。我非常确定所有其他可移植的和与stl兼容的机制都会做同样的事情(但可能看起来更漂亮)。

下面是一个使用新文件系统库的版本,它具有相当健壮的错误检查功能:

#include <cstdint>
#include <exception>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>

namespace fs = std::filesystem;

std::string loadFile(const char *const name);
std::string loadFile(const std::string &name);

std::string loadFile(const char *const name) {
  fs::path filepath(fs::absolute(fs::path(name)));

  std::uintmax_t fsize;

  if (fs::exists(filepath)) {
    fsize = fs::file_size(filepath);
  } else {
    throw(std::invalid_argument("File not found: " + filepath.string()));
  }

  std::ifstream infile;
  infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  try {
    infile.open(filepath.c_str(), std::ios::in | std::ifstream::binary);
  } catch (...) {
    std::throw_with_nested(std::runtime_error("Can't open input file " + filepath.string()));
  }

  std::string fileStr;

  try {
    fileStr.resize(fsize);
  } catch (...) {
    std::stringstream err;
    err << "Can't resize to " << fsize << " bytes";
    std::throw_with_nested(std::runtime_error(err.str()));
  }

  infile.read(fileStr.data(), fsize);
  infile.close();

  return fileStr;
}

std::string loadFile(const std::string &name) { return loadFile(name.c_str()); };

我没有足够的声誉来直接评论使用tellg()的响应。

请注意,tellg()会在出错时返回-1。如果将tellg()的结果作为分配参数传递,则应该首先对结果进行完整性检查。

这个问题的一个例子:

...
std::streamsize size = file.tellg();
std::vector<char> buffer(size);
...

在上面的例子中,如果tellg()遇到错误,它将返回-1。在signed(即tellg()的结果)和unsigned(即vector<char>构造函数的arg)之间隐式强制转换将导致your vector错误地分配大量字节。(可能是4294967295字节,或4GB。)

修改paxos1977的答案以解释上述问题:

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

    ifstream::pos_type fileSize = ifs.tellg();
    if (fileSize < 0)                             <--- ADDED
        return std::string();                     <--- ADDED

    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(&bytes[0], fileSize);

    return string(&bytes[0], fileSize);
}

由于这似乎是一个广泛使用的实用程序,我的方法是搜索并选择已经可用的库,而不是手工制作的解决方案,特别是如果boost库已经在您的项目中链接(链接器标志-lboost_system -lboost_filesystem)。在这里(以及旧的boost版本),boost提供了一个load_string_file实用程序:

#include <iostream>
#include <string>
#include <boost/filesystem/string_file.hpp>

int main() {
    std::string result;
    boost::filesystem::load_string_file("aFileName.xyz", result);
    std::cout << result.size() << std::endl;
}

作为一个优点,这个函数不寻求整个文件来确定大小,而是在内部使用stat()。然而,一个可能可以忽略不计的缺点是,在检查源代码时可以很容易地推断出:字符串不必要地用'\0'字符来调整大小,而'\0'字符是由文件内容重写的。