函数中堆分配与缺少堆分配的影响

Effect of heap allocation vs lack of heap allocation in a function

I've managed to write a program that implements the Game of Life using the Observer design pattern, but it seems that one of my functions, Grid::init() only works if I allocate the Cells on the heap and doesn't work otherwise. Below is the class definition for a Grid.

// 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 );
};

Cell是网格的一个单元,存储着它的位置、状态等信息。我尝试通过 vector 间接分配堆上的 Cell 来初始化网格,但这没有用;相反,当我改变 网格 class 的单元格元素是一个

std::vector> 实现有效。

我想我可能知道发生了什么。这段代码有几个问题,我想我已经弄清楚是什么问题组合导致了这种行为。

问题 #1: 类 具有不安全的复制构造函数/复制赋值运算符。

一般来说,任何使用指针的 class 都应该覆盖复制构造函数、复制赋值运算符和析构函数。简单的方法就是不使用指针,或者只使用智能指针,但这不是万灵药。

特别是,Grid 复制不安全。 std::vector<Cell *>std::vector<Cell> 版本都不安全……但出于不同的原因!

问题 #2: 这个奇怪的模式:

TextDisplay newDisplay(n);
td = new TextDisplay{newDisplay};

这构造了两个 TextDisplay个对象。一个是另一个的副本。通常,这可能只是创建一个对象的一种迂回或缓慢的方式,但只有当 class 没有上面的问题 #1 时才会出现这种情况。

导致问题的原因

如果您创建包含 std::vector<Cell>Grid 副本,所有单元格的地址都会改变!这不适用于 Cell *.

您应该通过删除 Grid 复制构造函数(和复制赋值运算符)来防止这种情况发生。

class Grid {
    Grid(const Grid &) = delete;
    Grid &operator=(const Grid &) = delete;
};

这可能会告诉您问题出在哪里。

摆脱裸指针

一般来说,现代 C++ 建议(自 2011 版以来)是使用智能指针而不是裸指针。

因此,请使用 std::unique_ptr<Cell>std::shared_ptr<Cell>

而不是 Cell *

这不一定适用于像 Subject 中的 std::vector<Observer *> 这样的“非拥有”指针,并且它不适用于内部指针(例如指向单个单元格的指针一个 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 |
+---------------------+     +--------+     +------+
                            |    ... |