我在一个项目中使用 Qt 6.5,并尝试重构/改进最初用 Qt 5.10 编写的树模型。我花了一些时间查看 Qt 的 SimpleTreeModel 示例,并成功使用代码创建了我自己的模型:
class TreeItem
{
public:
explicit TreeItem(QVariantList data, TreeItem *parentItem = nullptr);
void appendChild(std::unique_ptr<TreeItem> &&child);
TreeItem *child(int row);
int childCount() const;
int columnCount() const;
QVariant data(int column) const;
int row() const;
TreeItem *parentItem();
private:
std::vector<std::unique_ptr<TreeItem>> m_childItems;
QVariantList m_itemData;
TreeItem *m_parentItem;
};
但让我烦恼的是
std::vector<std::unique_ptr<TreeItem>>
的使用(在之前的 Qt 版本中为 std::vector<TreeItem*>
)。为什么使用它而不是使用更简单的std::vector<TreeItem>
?
我认为在这里使用指针没有任何意义,无论是基本的还是智能的,因为这增加了不必要的间接级别,并且对于具有大量项目的模型来说可能成本高昂。
动态容器中的项目可能会移动。在这个数据结构设计中,每个
TreeItem
都需要知道它的父级。如果 m_childItems
包含实际的 TreeItem
对象,则当向量调整大小时它们将移动到不同的地址。那时,孩子的父指针将成为悬空指针。需要一些更复杂/昂贵的方法来跟踪父项。
因此,每个
TreeItem
一旦创建就需要保留在固定的内存位置。简单的解决方案是,如果 m_childItems
包含指向从堆分配的项目的指针。
手动内存管理是现代 C++ 代码中的一大禁忌,这是有充分理由的,因为这是 C 和遗留 C++ 代码中错误的巨大来源。
std::unique_ptr
是 C++ 类,用于存储指向具有单一所有者且没有共享所有权的对象的指针。当 m_childItems
被破坏时,现在也会自动破坏其中的所有 TreeItem。这就是向量不包含原始指针的原因。
另一点是,
TreeItem
具有不小的复制/移动成本,因此如果m_childItems
包含实际对象而不是指针,那么调整大小的向量会稍微昂贵。仅复制/移动指针要快得多。
所以这就是为什么
m_childItems
包含指针,以及为什么在这种情况下指针是std::unique_ptr
。不同的用例或设计可能会使用/需要 std::shared_ptr
和 std::weak_ptr
,但如果不需要,拥有 std::unique_ptr
和原始非拥有指针会更简单、更好。