代码之家  ›  专栏  ›  技术社区  ›  Tommy Tsang

c++std::stringstream给了我奇怪的行为

  •  3
  • Tommy Tsang  · 技术社区  · 6 年前

    #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";
                    */
            }
    }
    

    我想让全班同学一行一行地读这个字符串。没有注释部分,我得到以下结果:

    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编译它。非常感谢你的帮助。

    2 回复  |  直到 6 年前
        1
  •  3
  •   alter_igel    6 年前

    当你写作时 IOLoader data1_loader = IOLoader((std::stringstream(data1))); ,您正在绑定 IOLoader::stream_ 暂时的 ,从那以后 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";
    }
    

    如果你需要 装载机 std::cin ,那么坚持引用成员是有意义的。请注意,引用的流需要在 stream_ 正在使用成员。否则,如果你只和 std::stringstream IOLoader::流_ std::move 通过r值引用传递给构造函数的流。

        2
  •  2
  •   Michael Veksler    6 年前

    传递一个rvalue引用并将其保留在周围是被破坏的,并且几乎肯定会导致沿途的未定义行为(UB)。我指的是以下代码,它有助于但不直接导致UB:

    IOLoader(std::istream&& t_stream) : stream_(t_stream) 
    { 
          for(int i = 0; i < 2; ++i) std::getline(stream_, line_);
    };// get rid of the header
    

    构造函数使以下行可以无提示地触发UB:

    for(IOLoader data1_loader = IOLoader((std::stringstream(data1))); !data1_loader.IsEnd(); data1_loader.Next())
    

    这一行创建一个临时(rvalue) stringstream IOLoader 接受rvalue引用的。但是接受rvalue引用的构造函数不移动任何内容,只存储对临时 . 这与通常使用rvalue引用(即移动对象)相反。当循环体开始时 弦流 已经被摧毁了,而且 stream_ Next() ,或者以任何其他方式,是UB。

    您可以通过创建 stingsstream 对象:

    std::stringstream tmp_stream(data1);
    for(IOLoader data1_loader = IOLoader(tmp_stream); !data1_loader.IsEnd(); data1_loader.Next())
    

    这将修复实例,但不会修复核心问题。核心问题是误导的存在。 && && 构造函数,或者将其全部移除,或者使其实际移动 弦流

    class IOLoader
    {
    ...
            IOLoader(std::stringstream&& t_stream) : saved_stream_(std::move(t_stream)), stream_(saved_stream_)
            { 
                    for(int i = 0; i < 2; ++i) std::getline(stream_, line_);
            };// get rid of the header
    
    ...
            std::stringstream saved_stream_;
            std::istream& stream_;
            std::string line_;
    };
    

    缺点是在这种情况下,它只能与 弦流 istringstream . 通过使用模板,可以使其更通用(附加堆分配的运行时成本):

    class IOLoader
    {
    public:
    ....    
            // enable_if avoids regular references, so that we neither prefer this ctor
            // over the other ctor, nor try to move from a regular lvalue reference.
            template <typename Stream, typename = typename std::enable_if<!std::is_reference<Stream>::value>::type>
            IOLoader(Stream&& t_stream) : saved_stream_(std::make_unique<typename std::decay<Stream>::type>(std::move(t_stream))), stream_(*saved_stream_)
            { 
                    for(int i = 0; i < 2; ++i) std::getline(stream_, line_);
            };
    ...
    
            std::unique_ptr<std::istream> saved_stream_;
            std::istream& stream_;
            std::string line_;
    };