函数中堆分配与缺少堆分配的影响
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 |
+---------------------+ +--------+ +------+
| ... |
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 | +---------------------+ +--------+ +------+ | ... |