以下代码给了我一些意外的行为:
#include <map>
#include <iostream>
#include <string>
#include <sstream>
const std::string data1 =
"column1 column2\n"
"1 3\n"
"5 6\n"
"49 22\n";
const std::string data2 =
"column1 column2 column3\n"
"10 20 40\n"
"30 20 10\n";
class IOLoader
{
public:
// accept an istream and load the next line with member Next()
IOLoader(std::istream& t_stream) : stream_(t_stream)
{
for(int i = 0; i < 2; ++i) std::getline(stream_, line_);
};// get rid of the header
IOLoader(std::istream&& t_stream) : stream_(t_stream)
{
for(int i = 0; i < 2; ++i) std::getline(stream_, line_);
};// get rid of the header
void Next()
{
// load next line
if(!std::getline(stream_, line_))
line_ = "";
};
bool IsEnd()
{ return line_.empty(); };
std::istream& stream_;
std::string line_;
};
int main()
{
for(IOLoader data1_loader = IOLoader((std::stringstream(data1))); !data1_loader.IsEnd(); data1_loader.Next())
{
std::cout << data1_loader.line_ << "\n";
// weird result if the following part is uncommented
/*
IOLoader data2_loader = IOLoader(std::stringstream(data2));
std::cout << data2_loader.line_ << "\n";
data2_loader.Next();
std::cout << data2_loader.line_ << "\n";
*/
}
}
我希望类IOLoader逐行读取字符串。我得到以下结果,但未注释部分:
1 3
5 6
49 22
这是完全可以预期的。问题是当我用data2_loader取消注释零件时会发生什么。现在它给了我:
1 3
10 20 40
30 20 10
mn349 22
10 20 40
30 20 10
我不知道发生了什么事。这是我最初的期望:
1 3
10 20 40
30 20 10
5 6
10 20 40
30 20 10
49 22
10 20 40
30 20 10
无论出于什么原因,如果我使用data2创建一个stringstream,就不会正确读取data1。我用g++ 4.9.2编译。非常感谢你的帮助。
最佳答案
当您编写IOLoader data1_loader = IOLoader((std::stringstream(data1)));
时,您会将IOLoader::stream_
引用成员绑定(bind)到一个临时对象,因为该std::stringstream(data1)
在构造函数之后被销毁了。您只剩下从悬而未决的引用中读取被破坏对象的信息,这是未定义的行为,因此绝对有可能发生任何事情。一个简单的解决方法是将stringstream
都声明为可以生存的变量,只要IOLoader
需要它们,并且删除您的IOLoader(std::istream&& t_stream)
构造函数,因为它实际上并不移动t_stream
,作为r值引用,它通常是临时的。
std::stringstream ss1 {data1};
for(IOLoader data1_loader = IOLoader(ss1); !data1_loader.IsEnd(); data1_loader.Next()){
std::cout << data1_loader.line_ << "\n";
std::stringstream ss2 { data2 };
IOLoader data2_loader = IOLoader(ss2);
std::cout << data2_loader.line_ << "\n";
data2_loader.Next();
std::cout << data2_loader.line_ << "\n";
}
如果您需要
IOLoader
与不能像std::cin
那样拥有所有权的流进行一般性的工作,那么坚持引用成员是有意义的。请注意,只要使用了stream_
成员,被引用的流就必须存在。否则,如果仅使用std::stringstream
,则最简单的方法是假定流拥有所有权并将IOLoader::stream_
设置为值类型。例如,您可以std::move
通过r值引用传递到构造函数的流。