c++ - Why does Qt's SimpleTreeModel example use std::vector<std::unique_ptr<>> instead of si

admin2025-04-26  4

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.

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.

Share Improve this question asked Jan 13 at 7:58 VincentVincent 5093 silver badges16 bronze badges 16
  • 1 I am not familiar with it, but could it be that TreeItem has derived classes and they use the pointer in order to utilize polymorphism (just a guess) ? – wohlstad Commented Jan 13 at 8:11
  • 1 For an interesting twist, How can I declare a member vector of the same class? asks how it is possible that a std::vector<TreeItem> is an option (since C++17 -- How old is the SimpleTreeModel example?). – JaMiT Commented Jan 13 at 9:12
  • 1 @Vincent Actually, the 5.15 currently shows QVector (this version was apparently introduced in 5.13, before that it used QList). – musicamante Commented Jan 13 at 11:23
  • 1 Now it makes sense. But then again, you need to provide a copy(+move) constructor and assignment operator(copy±move), and a =default destructor for stylistic correctness. In general, that's the scheme of self-referencing types(rule 3 or 5, instead of 0). – Red.Wave Commented Jan 14 at 15:30
  • 1 So, the role of uniqu_ptr is replace the rule 3/5(not 0), but at the cost of memory allocations and loss of locality. – Red.Wave Commented Jan 14 at 15:50
 |  Show 11 more comments

1 Answer 1

Reset to default 0

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.

转载请注明原文地址:http://anycun.com/QandA/1745662497a91064.html