I'm using Qt 6.5 in a project and trying to refactor/improve a tree model that was initially written in Qt 5.10. I spent some time looking at Qt's SimpleTreeModel example and succesfully use the code to create my own model:
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;
};
But something that bugs me is the use of std::vector<std::unique_ptr<TreeItem>>
(std::vector<TreeItem*>
in previous Qt versions). Why use that instead of using a more simple std::vector<TreeItem>
?
I see no point using a pointer here, either basic or smart, as this adds a level of indirection that isn't necessary and could be costly for models with a lot of items.
Items in dynamic containers may get moved around. Each TreeItem
needs to know its parent, in this data structure design. If m_childItems
contained actual TreeItem
objects, they would move to a different address when the vector gets resized. At that point, the parent pointers of their child would become dangling pointers. Some more complex/expensive method would be needed to keep track of parent items.
So each TreeItem
needs to stay at a fixed memory location once it is created. Simple solution is, if m_childItems
contains pointers to items allocated from heap.
Manual memory management is a big no-no in modern C++ code, and for a good reason, as that is a huge source of bugs in C and legacy C++ code. std::unique_ptr
is the C++ class to store a pointer to an object which has a single owner and no shared ownership. When m_childItems
gets destructed, all std::unique_ptrs in it are destructed too, automatically, and they delete the TreeItems.
Another, in this case minor point is, TreeItem
has non-trivial copy/move cost, so if m_childItems
contained actual objects instead of pointers, then the vector getting resized would be slightly expensive. Copying/moving just pointers is a lot faster.
So this is why m_childItems
contains pointers, and why the pointers are std::unique_ptr
in this case. A different use case or design might use/need std::shared_ptr
and std::weak_ptr
, but if there is no need for that, owning std::unique_ptr
and raw non-owning pointers is simpler and better.