Эффект распределения кучи против отсутствия выделения кучи в функции - PullRequest
0 голосов
/ 13 июля 2020

Мне удалось написать программу, реализующую Game of Life с использованием шаблона проектирования Observer, но кажется, что одна из моих функций, Grid :: init (), работает только в том случае, если я размещаю ячейки на куча и иначе не работает. Ниже приведено определение класса для сетки.

// Grid class
// Grid is initially empty. Sets up a TextDisplay observer when init() is called
// and the grid is initialized with cell objects.
class Grid {
    TextDisplay *td = nullptr;         
    std::vector<std::vector<Cell>> cells; 
    size_t size; 
 
  public:
    ~Grid(); 
    void setObserver(Observer *ob); 
  
    void init( size_t n );              
    void turnOn( size_t r, size_t c );  
    void tick();                        
    
    friend std::ostream & operator<<( std::ostream & out, const Grid & g );
};

Ячейка - это единица сетки, в которой хранится такая информация, как ее положение и состояние. Я попытался инициализировать сетку, косвенно распределив ячейки в куче через вектор, но это не сработало; вместо этого, когда я изменил элемент ячейки класса сетки на

std :: vector > реализация сработала.

1 Ответ

3 голосов
/ 13 июля 2020

Думаю, я мог бы знать, что происходит. Есть несколько проблем с этим кодом, и я думаю, что выяснил, какая комбинация проблем вызывает такое поведение.

Проблема №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 |
+---------------------+     +--------+     +------+
                            |    ... |
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...