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

问题描述 投票:0回答:1

这里有一个简单的代码片段来学习命令模式,它是模拟一个简单的编辑器。 但我真的不明白为什么它在插入时崩溃。回溯似乎告诉我,当调用插入时,

Document::content
不是无效的

#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) 
c++ string c++11
1个回答
0
投票

参见第 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


如果您启用警告,编译器可能会为您捕获它。 我的编译器立即告诉我代码有些问题:

<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){}
      |            ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
© www.soinside.com 2019 - 2024. All rights reserved.