以下代码给了我一些意外的行为:

#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值引用传递到构造函数的流。

10-07 17:16