这里有一个简单的代码片段来学习命令模式,它是模拟一个简单的编辑器。 但我真的不明白为什么它在插入时崩溃。回溯似乎告诉我,当调用插入时,
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)
参见第 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){}
| ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~