Я пытался полностью переупаковать шаблон отмены, демонтированный в Sean Parent's talk "Inheritance Is The Base Class of Evil". Разговор охватывает несколько оснований, включая семантику перемещения C++, и использование понятий для реализации полиморфизма вместо наследования, но шаблон хранения дельта-отмены - тот, который я пытался раздобыть. Вот рабочая адаптация примера Родитель дал в своей речи:Как я могу адаптировать этот шаблон стека повторного использования хранилища от общего вектора к типизированной модели?
#include <iostream>
#include <memory>
#include <vector>
#include <assert.h>
using namespace std;
template <typename T>
void draw(const T& x, ostream& out, size_t position)
{
out << string(position, ' ') << x << endl;
}
class object_t {
public:
template <typename T>
object_t(T x) : self_(make_shared<model<T>>(move(x))) {}
friend void draw(const object_t& x, ostream& out, size_t position)
{ x.self_->draw_(out, position); }
private:
struct concept_t {
virtual ~concept_t() = default;
virtual void draw_(ostream&, size_t) const = 0;
};
template <typename T>
struct model : concept_t {
model(T x) : data_(move(x)) { }
void draw_(ostream& out, size_t position) const
{ draw(data_, out, position); }
T data_; };
shared_ptr<const concept_t> self_;
};
// The document itself is drawable
using document_t = vector<object_t>;
void draw(const document_t& x, ostream& out, size_t position)
{
out << string(position, ' ') << "<document>" << endl;
for (const auto& e : x) draw(e, out, position + 2);
out << string(position, ' ') << "</document>" << endl;
}
// An arbitrary class
class my_class_t {
/* ... */
};
void draw(const my_class_t&, ostream& out, size_t position)
{ out << string(position, ' ') << "my_class_t" << endl; }
// Undo management...
using history_t = vector<document_t>;
void commit(history_t& x) { assert(x.size()); x.push_back(x.back()); }
void undo(history_t& x) { assert(x.size()); x.pop_back(); }
document_t& current(history_t& x) { assert(x.size()); return x.back(); }
// Usage example.
int main(int argc, const char * argv[])
{
history_t h(1);
current(h).emplace_back(0);
current(h).emplace_back(string("Hello!"));
draw(current(h), cout, 0);
cout << "--------------------------" << endl;
commit(h);
current(h).emplace_back(current(h));
current(h).emplace_back(my_class_t());
current(h)[1] = string("World");
draw(current(h), cout, 0);
cout << "--------------------------" << endl;
undo(h);
draw(current(h), cout, 0);
return EXIT_SUCCESS;
}
Вместо отслеживания отмены в стеке команд, которые захватывают их до и после состояний, этот шаблон треков отвинчивать состояния в виде стопки «целых документов «где каждая запись, по сути, является полной копией документа. Фокус шаблона заключается в том, что хранение/выделение происходит только для частей документа, которые различаются между каждым состоянием, с использованием некоторой косвенности и shared_ptr
. Каждая «копия» только берет на себя штраф за хранение за то, что отличается от предыдущего состояния.
Образец в примере родителя показывает, что «текущий» документ полностью изменчив, но привязывается к истории, когда вы вызываете commit
в историю. Это подталкивает «копию» текущего состояния к истории.
Реферат, я нахожу этот образец неотразимым. Пример Parent, представленный в этом разговоре, был явно надуман в первую очередь, чтобы продемонстрировать свои соображения о концептуальном полиморфизме и семантике перемещения. Относительно тех, шаблон отмены выглядит вспомогательным, хотя я считаю, что его роль заключается в том, чтобы указать значение семантики значения.
В этом примере документ «модель» - это всего лишь «вектор объектов, соответствующих концепции». Это послужило его целью для демонстрации, но мне сложно экстраполировать «вектор понятий» на «реальный мир, типизированная модель». (Давайте просто скажем, что для целей этого вопроса концептуальный полиморфизм не имеет значения.) Так, например, рассмотрим следующую тривиальную модель, где «документ» - это company
с некоторым числом employees
, каждый с именем, заработная плата, и картина:
struct image {
uint8_t bitmapData[640 * 480 * 4];
};
struct employee {
string name;
double salary;
image picture;
};
struct company {
string name;
string bio;
vector<employee> employees;
};
у меня есть вопрос: Как я могу ввести окольные необходимую для совместного использования памяти, не теряя способность взаимодействовать с моделью непосредственно и просто? По простоте взаимодействия я имею в виду, что вы должны иметь возможность продолжать взаимодействие с моделью прямолинейно без большого количества RTTI или кастингов и т. Д. Например, если вы пытались дать каждому, кто назван «Сьюзан», 10% рейз , захватывая состояние отмены после каждого изменения, простое взаимодействие может выглядеть примерно так:
using document_t = company;
using history_t = vector<document_t>;
void commit(history_t& x) { assert(x.size()); x.push_back(x.back()); }
void undo(history_t& x) { assert(x.size()); x.pop_back(); }
document_t& current(history_t& x) { assert(x.size()); return x.back(); }
void doStuff()
{
history_t h(1);
for (auto& e : current(h).employees)
{
if (e.name.find("Susan") == 0)
{
e.salary *= 1.1;
commit(h);
}
}
}
хитрость, кажется, вводя окольные предоставленный object_t
, но это не ясно, как я могу и представить необходимую косвенность и затем попеременно проходят эту косвенную передачу. Я вообще могу обойтись в коде на C++, но это не мой повседневный язык, так что это может быть очень просто. Несмотря на это, неудивительно, что пример Родитель не охватывает это, так как значительная часть его точки заключалась в способности скрыть использование понятий.
У кого-нибудь есть мысли по этому поводу?
Косвенная идея ссылки крутая. При таком подходе модель документа действительно может быть просто вектором - поместить корневой объект в индекс 0, перейти оттуда. Производительность поражает, но по крайней мере хитом является постоянное время. COW казался хорошим подходом к моему разуму, но кажется, что в современном C++-ландшафте существует много ненависти к COW из-за первоочередных последствий COW в многопоточных средах. Спасибо за мысли! – ipmcc