从函数返回 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
为什么会发生这种情况,我该如何解决?是否找到文件无关紧要,两种情况都会发生。
【问题讨论】:
inputStream
在getStreamFrom
结束时被销毁。所以指向它的指针是无效的。
您正在返回一个局部变量的地址 - 这会导致 未定义的行为!例如,请参见:Can a local variable's memory be accessed outside its scope?。
即使您解决了istream
在函数返回时不再存在的问题,您也需要注意file_buf
,因为它在函数返回时也不再存在 - 独立于istream
。将缓冲区的地址传递给istream
不会改变这一点。
【参考方案1】:
如果要从函数返回本地流,则需要动态分配流及其缓冲区。正如 cmets 和答案所解释的那样,您正在返回指向函数退出后将被销毁的局部变量的指针。标准流类std::istream
和std::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::istream
是std::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锁定