从函数返回 istream 的正确方法

Posted

技术标签:

【中文标题】从函数返回 istream 的正确方法【英文标题】:Correct way to return istream from function 【发布时间】:2019-12-22 19:39:59 【问题描述】:

我正在测试从 istream 读取各种数据技术的速度,因此我创建了一个函数来重复从文件创建 istream,以便测试的下一个方法可以使用该 istream。

using std::istream;
using std::string;

istream* getStreamFrom(string filepath)

    std::filebuf init_buffer;
    if (init_buffer.open(filepath, std::ios::in))
    
        std::istream inputStream(&init_buffer);
        init_buffer.close();
        return &inputStream;
    

    // file not found
    istream inputStream(0);
    return &inputStream;

然后我将 istream 传递给经过测试的方法,如下所示:

istream* data = getStreamFrom(FILEPATH);
someMethod(*data);

但是在someMethod 从 istream 读取时出现访问冲突:

void someMethod(istream& input)

    string line;
    while (std::getline(input, line))    // code failes here
        // do something

为什么会发生这种情况,我该如何解决?是否找到文件无关紧要,两种情况都会发生。

【问题讨论】:

inputStreamgetStreamFrom 结束时被销毁。所以指向它的指针是无效的。 您正在返回一个局部变量的地址 - 这会导致 未定义的行为!例如,请参见:Can a local variable's memory be accessed outside its scope?。 即使您解决了istream 在函数返回时不再存在的问题,您也需要注意file_buf,因为它在函数返回时也不再存在 - 独立于istream。将缓冲区的地址传递给istream 不会改变这一点。 【参考方案1】:

如果要从函数返回本地流,则需要动态分配流及其缓冲区。正如 cmets 和答案所解释的那样,您正在返回指向函数退出后将被销毁的局部变量的指针。标准流类std::istreamstd::ostream 不可移动,因此您唯一的选择是在堆上创建对象。流和缓冲区都可以包装在智能指针中,以避免手动内存管理。

std::unique_ptr<std::istream> getStreamFrom(std::string filepath) 
  auto init_buf = std::make_unique<std::filebuf>();
  return std::make_unique<std::istream>(
    init_buf->open(filepath, std::ios_base::in)
      ? init_buf.release()->close()
      : nullptr
  );

顺便说一句,用std::filebuf 初始化的std::istreamstd::fstream。你也可以返回一个指向它的指针:

std::unique_ptr<std::ifstream> getStreamFrom(std::string filepath) 
  auto stream = std::make_unique<std::ifstream>(filepath);
  stream->close();
  return std::unique_ptr<std::ifstream>( *stream ? std::move(stream) : nullptr );

在 C++17 中可选:

std::optional<std::ifstream> getStreamFrom(std::string filepath) 
  std::ifstream stream(filepath);
  stream.close();
  return stream ? std::optional(std::move(stream)) : std::nullopt;

【讨论】:

为什么在返回之前关闭流?所做的只是强制调用者重新打开流。 @Peter 因为这就是 OP 所做的。我试图尽可能多地复制他的代码。 @0x499602D2 是的。我发现无论你做什么,如果你在返回 istream 之前关闭缓冲区,你将再次违反。所以解决方案是要么围绕 istream-buffer 制作全新的包装器,要么在主代码中创建 istream 而无需函数调用。 @sanitizedUser 你在说什么违规行为? @0x499602D2 我已经删除了代码,但它说读取关闭缓冲区不是一个好主意。【参考方案2】:

inputStream 一旦你的函数退出,就会失去作用域。在函数外访问std::istream* 将导致未定义的行为。

可能最好的方法是使用std::shared_ptr。像这样的:

std::shared_ptr<std::istream> getStreamFrom(string filepath)

    std::filebuf init_buffer;
    if (init_buffer.open(filepath, std::ios::in))
    
        std::shared_ptr<std::istream> inputStream = std::make_shared<std::istream>(&init_buffer);
        init_buffer.close();
        return inputStream;
    

    // file not found
    std::shared_ptr<std::istream> inputStream = std::make_shared<istream>(nullptr);
    return inputStream;

当然,init_buffer 也即将超出范围。不太确定在这里做什么是正确的,就像passing it directly into std::istream won't delete the buffer。我不知道这里的正确方法是什么,因为您没有自己制作std::istream,因此建议的解决方案对您不起作用。所以你有点靠你自己。但是你已经被警告了。

您可以做的一件事是 std::move() 它。这是一些more information on std::move()ing things。

【讨论】:

以上是关于从函数返回 istream 的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

调用'get'和'peek'时阻止c ++ istream锁定

C++ 从 istream 读取 int,检测溢出

在 Windows 中将 FILE * 或 HANDLE 转换(分配)到 IStream

cin函数的返回值是啥?

cppPrimer学习8th

使用重载输入流运算符读取带字符串的文件>>