C ++ std :: stringstream给我怪异的行为

时间:2018-08-14 20:57:56

标签: c++ stringstream

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

ActionListener ban = new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        int fila = table.getSelectedRow();
        String nombre = (String) modelo.getValueAt(fila, 0);
        modelo.removeRow(fila);
        try {
            removeUser(nombre);
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
};
btnBanear.addActionListener(ban);

...

public void removeUser(String nombre) throws IOException {
    String lee = null;
    String usuario = "";
    CharSequence aux = nombre;
    try {
        registro = new File("Registro.txt");
        tempFile = new File("Registro1.txt");
        lector = new BufferedReader(new FileReader(registro));
        fw = new FileWriter(tempFile);
        writer = new BufferedWriter(fw);
    } catch (FileNotFoundException ex) {
        System.out.println(ex.getMessage());
    } catch (IOException ex) {
        System.out.println(ex.getMessage());
    } catch (NullPointerException e) {
        System.out.println(e.getMessage());
    }

    while ((lee = lector.readLine()) != null) {
        System.out.println(aux);
        if (lee.contains(nombre)) {
            continue;
        } else {
            writer.write(lee);
        }
    }
    lector.close();
    fw.close();
    writer.close();
    registro.delete();
}

我希望IOLoader类逐行读取字符串。我得到以下结果,但未注释部分:

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

这是完全可以预期的。问题是当我用data2_loader取消注释零件时会发生什么。现在它给了我:

1       3
5       6
49      22

我不知道发生了什么。这是我最初的期望:

1       3
10      20      40
30      20      10
mn349   22
10      20      40
30      20      10

由于任何原因,如果我使用data2创建一个字符串流,则data1均无法正确读取。我用g ++ 4.9.2编译它。非常感谢您的帮助。

2 个答案:

答案 0 :(得分:3)

编写IOLoader data1_loader = IOLoader((std::stringstream(data1)));时,会将IOLoader::stream_引用成员绑定到临时,因为该std::stringstream(data1)在构造函数之后被销毁。您只剩下从悬空引用到已破坏对象的阅读,这是未定义的行为,因此绝对有可能发生任何事情。一个简单的解决方法是将两个stringstream都声明为可以持续IOLoader需要它们的变量,并删除您的IOLoader(std::istream&& t_stream)构造函数,因为它实际上并没有移动{{1} },作为r值参考,通常是临时的。

t_stream

如果您需要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_通过r值引用传递到构造函数的流。

答案 1 :(得分:2)

传递右值引用并将其保留在周围已损坏,几乎可以肯定会导致未定义的行为(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())

此行创建一个临时(右值)stringstream对象。由于这是一个右值,因此它的引用会愉快地传递给接受右值引用的IOLoader的构造函数。但是接受rvalue-reference的构造函数不会移动任何东西,而只是存储对临时stringstream的引用。这与右值引用的正常用法相反,后者是移动对象。在循环主体开始时,临时stringstream已被破坏,并且stream_指的是被破坏的对象。在Next()中或以任何其他方式使用此类引用是UB。

您可以通过创建一个名为stingsstream的对象来修复此错误实例:

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

这将修复实例,但不会解决核心问题。核心问题是存在误导性的&&构造函数。 &&构造函数有两种选择,要么将其全部删除,要么使其实际上移动stringstream

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_;
};

缺点是,在这种情况下,它将仅适用于stringstream,而不适用于类似类型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_;
};

在我看来,这对于一次性使用而言太复杂了,除非大量的代码将要使用它,否则我将使用右值引用抛弃构造函数,而不是对其进行修复。