Думаю, я мог бы знать, что происходит. Есть несколько проблем с этим кодом, и я думаю, что выяснил, какая комбинация проблем вызывает такое поведение.
Проблема №1: Классы имеют небезопасные конструкторы копирования / операторы присваивания копий.
В общем, любой класс, использующий указатели, должен переопределять конструктор копирования, оператор присваивания копии и деструктор. Самый простой способ сделать это - просто не использовать указатели или просто использовать интеллектуальные указатели, но это не панацея.
В частности, Grid
небезопасно копировать. Обе версии std::vector<Cell *>
и std::vector<Cell>
небезопасны ... но по разным причинам!
Проблема № 2: Этот любопытный шаблон:
TextDisplay newDisplay(n);
td = new TextDisplay{newDisplay};
Это создает два TextDisplay
объекта. Один является копией другого. Обычно это может быть просто окольный или медленный способ создания одного объекта, но это только в том случае, если у класса нет проблемы №1 выше.
Что вызывает проблему
Если вы создадите копию Grid
, содержащую std::vector<Cell>
, адреса всех ячеек изменятся! Это не относится к Cell *
.
Вы должны предотвратить это, удалив конструктор копирования Grid
(и оператор присваивания копии).
class Grid {
Grid(const Grid &) = delete;
Grid &operator=(const Grid &) = delete;
};
Это может показать вам где проблема.
Избавьтесь от голых указателей
В общем, современный совет C ++ (начиная с издания 2011 года) заключается в использовании интеллектуальных указателей вместо простых указателей.
Итак, вместо Cell *
используйте std::unique_ptr<Cell>
или std::shared_ptr<Cell>
.
Это не обязательно относится к «чужим» указателям, таким как std::vector<Observer *>
внутри Subject
, и это не так. t работает для внутренних указателей (например, указатель на одну ячейку в std::vector<Cell>
), но…
Вам, вероятно, следует использовать std::vector<std::vector<std::unique_ptr<Cell>>>
вместо std::vector<std::vector<Cell *>>
Вам, вероятно, следует использовать std::unique_ptr<TextDisplay>
вместо TextDisplay *
.
В общем, если вы можете найти способ избежать звонка new
, избегайте вызова new
.
Примечание о куче
Обратите внимание, что std::vector<Cell>
и std::vector<Cell *>
оба выделяются в куче. Просто std::vector<Cell *>
имеет дополнительный уровень косвенности.
Heap
+-------------------+ +------+
| std::vector<Cell> | --> | Cell |
+-------------------+ +------+
| ... |
Heap
+---------------------+ +--------+ +------+
| std::vector<Cell *> | --> | Cell * | --> | Cell |
+---------------------+ +--------+ +------+
| ... |