Почему код вылетает при вызове std::string::begin()?

Вот простой фрагмент кода для изучения шаблона команд, который имитирует простой редактор. Но я действительно не могу понять, почему происходит сбой при вставке. Кажется, обратная трассировка говорит мне, что 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) 
Insert никогда не инициализирует свой m_doc_ptr член. Затем вызывает через нулевой указатель. Конструктор Insert не использует свой параметр doc_ptr. В трассировке стека this=0x0 в Document::insert (this=0x0... — это мертвая раздача.
Igor Tandetnik 02.05.2024 16:07

Включите эти предупреждения и обратите на них внимание. Используйте такие инструменты, как AddressSanitizer, чтобы помочь вам найти подобные проблемы. godbolt.org/z/x9s1WGdrq

Retired Ninja 02.05.2024 16:13
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
98
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

См. кадр 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){}
      |            ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~

Другие вопросы по теме