代码之家  ›  专栏  ›  技术社区  ›  John

为什么在调用std::string::begin()时代码崩溃?

  •  0
  • John  · 技术社区  · 2 年前

    这里有一个简单的代码片段来学习命令模式,这是模拟一个简单编辑器。 但我真的不明白为什么它在插入时会崩溃。回溯似乎告诉我 the Document::content is not vaid when inserting is called .

    #include <iostream>
    #include <string>
    #include <cstdint>
    #include <algorithm>
    #include <cstdint>
    #include <memory>
    #include <functional>
    #include <deque>
    #include <limits>
    #include <sstream>
    
    class Document  
    {
    public:
        enum class DocPos:uint8_t
        {
            DOC_END,
            DOC_BGN,
            DOC_MID,
        };
    
        int insert(size_t row, size_t col, const std::string str, size_t& ins_pos)
        {
            size_t pos = 0;
            ins_pos = std::string::npos;
            for(int i = 0; i<row-1; i++)
            {
                pos = content.find('\n', pos);  //for simpleness, just consider '\n' other than `\r\n` and etc.
                if(std::string::npos == pos)
                {
                    return -1;
                }
            }
        
            std::cout << "LOC A " << pos << " col=" << col << std::endl;
            auto itr = content.begin()+pos;
            std::cout << "*itr " << *itr << std::endl;
            for(int j=0; j<col-1; j++)
            {
                std::cout << "*itr " << *itr << std::endl;
                if(*itr++=='\n')
                {
                    return -1;
                }
            }
    
            ins_pos = pos+col-1;
            std::cout << ins_pos << std::endl;
            content.insert(ins_pos, str);
            return 0;
        }
    
        int append(const std::string& str)
        {
            content += str;
            return 0;
        }
    
        int erase(std::size_t length, DocPos pos = DocPos::DOC_END, std::size_t offset=0)
        {
            switch(pos)
            {
                case DocPos::DOC_END:
                    if(content.size() < length)
                    {
                        return -1;
                    }
                    content.erase(content.end()-length, content.end());
                    break;
                case DocPos::DOC_BGN:
                    if(content.size() < length)
                    {
                        return -1;
                    }
                    content.erase(content.begin(), content.begin()+offset);
                    break;
                case DocPos::DOC_MID:
                    if(std::distance(content.begin()+offset, content.end()) < length)
                    {
                        return -1;
                    }
    
                    content.erase(content.begin()+offset, content.begin()+offset+length);
                    break;
                default:
                    return -2;
            }
    
            return 0;
        }
    
        friend std::ostream& operator<<(std::ostream& os, const Document& doc)
        {
            os << doc.content;
            return os;
        }
    private:
        std::string content;
    };
    
    class ICommand
    {
    public:
        virtual int execute()=0;
        virtual int unexecute()=0;
        virtual ~ICommand() = default;
    };
    
    class Append final:  public ICommand
    {
    public:
        Append(std::shared_ptr<Document> doc_ptr, const std::string& str):m_doc_ptr(doc_ptr), m_str(str){};
        int execute() override
        {
            m_doc_ptr->append(m_str);
            return 0;
        }
        int unexecute() override
        {
            m_doc_ptr->erase(m_str.length(), Document::DocPos::DOC_END);
            return 0;
        }
        ~Append() 
        {
            //std::cout << "append resource is released, str=" << m_str << std::endl;
        }
    private:
        std::shared_ptr<Document> m_doc_ptr;
        std::string m_str;
    };
    
    class Insert final: public ICommand
    {
    public:
        Insert(std::shared_ptr<Document> doc_ptr, size_t row, size_t col, const std::string str):m_row(row),m_col(col),m_str(str),m_ins_pos(std::string::npos){}
        int execute() override
        {
            std::size_t pos = std::string::npos;
            return m_doc_ptr->insert(m_row, m_col, m_str, m_ins_pos);
        }
    
        int unexecute() override
        {
            if(std::string::npos == m_ins_pos)
            {
                return -1;
            }
    
            return m_doc_ptr->erase(m_str.length(), Document::DocPos::DOC_MID, m_ins_pos);
        }
    private:
        std::shared_ptr<Document> m_doc_ptr;
        size_t m_row;
        size_t m_col;
        std::string m_str;
        size_t m_ins_pos;
    };
    
    class Invoker
    {
    public:
        Invoker() = default;
    
        int execute(std::shared_ptr<ICommand> cmd)
        {
            cmd->execute();
            m_history.push_back(cmd);
    
            m_redo_list.clear();
    
            return 0;
        }
        
        int undo()
        {
            if(m_history.empty())
            {
                return -1;
            }
    
            auto& undo = m_history.back();
            undo ->unexecute();      
            m_redo_list.push_back(std::move(undo));
            m_history.pop_back();
    
            return 0;
        }
        int redo()
        {
            if(m_redo_list.empty())
            {
                return -1;
            }
    
            auto& redo = m_redo_list.back();
            redo ->execute();      
            m_history.push_back(std::move(redo));
            m_redo_list.pop_back();
    
            return 0;
        }
    private:
        std::deque<std::shared_ptr<ICommand>> m_history;
        std::deque<std::shared_ptr<ICommand>> m_redo_list;
    };
    
    int main()
    {
        auto diary_ptr = std::make_shared<Document>();
        auto report_ptr = std::make_shared<Document>();
    
        auto diary_editor = std::unique_ptr<Invoker>(new Invoker);
        auto report_editor = std::unique_ptr<Invoker>(new Invoker);
    
        report_editor->execute(std::make_shared<Append>(report_ptr, "Work Report\n"));
    
        diary_editor->execute(std::make_shared<Append>(diary_ptr, "5/2/2024 "));
        diary_editor->execute(std::make_shared<Append>(diary_ptr, "thursday"));
        diary_editor->execute(std::make_shared<Append>(diary_ptr, "\n"));
        diary_editor->execute(std::make_shared<Append>(diary_ptr, "A white doggy lies in the sunshine."));
        diary_editor->execute(std::make_shared<Append>(diary_ptr, "An old man worrys about the coming strom repoted by the weather report."));
        std::cout << *diary_ptr << std::endl;
    
        std::cout << "===undo" << std::endl;
        diary_editor->undo();
        std::cout << *diary_ptr << std::endl;
    
        std::cout << "===append" << std::endl;
        diary_editor->execute(std::make_shared<Append>(diary_ptr, "Some children run after a scaried cat."));
        std::cout << *diary_ptr << std::endl;
    
        std::cout << "===insert" << std::endl;
        diary_editor->execute(std::make_shared<Insert>(diary_ptr, 1, 10, " rainy"));
        std::cout << *diary_ptr << std::endl;
    
        report_editor->execute(std::make_shared<Append>(report_ptr, "1. implenments done\n"));
        report_editor->execute(std::make_shared<Append>(report_ptr, "2. unit tests done\n"));
    
        auto test_undo_redo = [](std::unique_ptr<Invoker>& editor, std::shared_ptr<Document>& doc_ptr, int times){
            std::stringstream org_ss;
            org_ss << *doc_ptr;
    
    
            for(int i=0; i<times; i++)
            {
                editor->undo();
            }
    
            for(int i=0; i<times; i++)
            {
                editor->redo();
            }
    
            std::stringstream cur_ss;
            cur_ss << *doc_ptr;
    
            return cur_ss.str() == org_ss.str();
        };
    
        if(!test_undo_redo(diary_editor, diary_ptr, 3))
        {
            std::cout << "not euqal" << std::endl;
        }
    
        if(!test_undo_redo(report_editor, report_ptr, 2))
        {
            std::cout << "not euqal" << std::endl;
        }
    
        std::cout << *report_ptr << std::endl;
    
        return 0;
    }
    

    以下是程序崩溃时的日志:

    (gdb) bt
    #0  0x00007ffff7ed4e74 in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char>, std::allocator<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) ()
       from /lib/x86_64-linux-gnu/libstdc++.so.6
    #1  0x0000555555557497 in Document::insert (this=0x0, row=1, col=10, 
        str=" rainy", ins_pos=@0x55555557bfe8: 18446744073709551615) at main.cpp:36
    #2  0x0000555555557d26 in Insert::execute (this=0x55555557bfa0) at main.cpp:140
    #3  0x0000555555557e0f in Invoker::execute (this=0x55555557af30, 
        cmd=std::shared_ptr<ICommand> (use count 1, weak count 0) = {...})
        at main.cpp:167
    #4  0x0000555555556ad1 in main () at main.cpp:223
    (gdb) 
    
    1 回复  |  直到 2 年前
        1
  •  4
  •   Yksisarvinen    2 年前

    见第1帧

    #1  0x0000555555557497 in Document::insert (this=0x0, row=1, col=10, 
                                                ^~~~~~~~
    

    Document 用于呼叫 insert 上是 nullptr . 让我们看看为什么

    #2  0x0000555555557d26 in Insert::execute (this=0x55555557bfa0) at main.cpp:140
    

    main.cpp:140 将是这条线:

            return m_doc_ptr->insert(m_row, m_col, m_str, m_ins_pos);
    

    好吧,那在哪里 m_doc_ptr 初始化?事实并非如此。构造函数不使用 doc_ptr 无论如何。


    如果您启用了警告,编译器可能会帮您发现它。 My compiler 立即告诉我代码有问题:

    <source>: In constructor 'Insert::Insert(std::shared_ptr<Document>, size_t, size_t, std::string)':
    <source>:135:38: warning: unused parameter 'doc_ptr' [-Wunused-parameter]
      135 |     Insert(std::shared_ptr<Document> doc_ptr, size_t row, size_t col, const std::string str):m_row(row),m_col(col),m_str(str),m_ins_pos(std::string::npos){}
          |            ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~