I have experience in "old-fashioned" C++ programming (i.e. I care myself about pointers and memory management). I do want to make use of modern concepts though.
Since my application makes heavy use of Qt, I'd like to use Qt's smartpointers. I am however somewhat confused about smartpointers in general, and their use in Qt.
1.) As far as I understand, if I derive from QObject
, I should better stick to Qt's object tree and ownership model and forget about smartpointers. Correct?
2.) In C++ I can get by with std::shared_ptr
and std::unique_ptr
. What are the equivalent smart pointers in Qt?
Suppose I have the following code:
QList<MyObject *> * foobar(MyOtherObject *ptr) {
// do some stuff with MyOtherObject
QList<MyObject* > ls = new QList<MyObject*>();
for(int i=0;i<10;i++) {
MyObject* mi = new MyObject();
...
ls.insert(mi);
}
return ls;
}
int main() {
MyOtherObject* foo = new MyOtherObject();
QList<MyObject*> *bar = foobar(foo);
// do stuff
// and don't care about cleaning up?!
}
3.) How to translate the above snippet into a version using smartpointers?
4.) In particular: Should I change function signature into using smartpointers? It seems to create quite complex type signatures (return type and passed arguments). Also what if some "legacy" function calls another function - is it better to write function signatures with raw pointers, and use smartpointers only "inside" functions?
5.) What smartpointer should replace ls
in the function foobar
? What is the pointer type that should be used for mi
, i.e. the objects stored in the QList
?
You are pretty much forced to use Qt
idiom of owning raw pointers for GUI objects, as QWidget
derived types will assume ownership of child elements.
Elsewhere, you should as much as possible avoid using any type of pointer. Most of the time you can pass a reference. If you need polymorphic ownership, then use std::unique_ptr
. In very rare cicrcumstances, you have multiple independent lifetimes that need ownership of a shared resource, for which you use std::shared_ptr
.
The Qt collection classes also interact badly with modern C++ constructs, e.g.
extern QList<Foo> getFoos();
for (const Foo & foo : getFoos())
{ /*foo is a dangling reference here*/ }
for (const Foo & foo : std::as_const(getFoos()))
{ /*This is safe*/ }
Your snippet would be
std::vector<std::unique_ptr<MyObject>> foobar(MyOtherObject & obj) {
// do some stuff with MyOtherObject
std::vector<std::unique_ptr<MyObject>> ls;
for(int i=0;i<10;i++)
{
ls.emplace_back(std::make_unique<MyObject>());
...
}
return ls;
}
int main() {
MyOtherObject foo = MyOtherObject;
auto bar = foobar(foo);
// do stuff
// destructors do the cleanup automatically
}
First of all, modern C++ allows you to use values, and Qt supports that. Thus, the default should be to use QObject
s as if they were non-movable, non-copyable values. Your snippet, in fact, requires no explicit memory management at all. And this does not clash at all with the fact that the objects have a parent.
#include <QObject>
#include <list>
using MyObject = QObject;
using MyOtherObject = QObject;
std::list<MyObject> makeObjects(MyOtherObject *other, QObject *parent = {}) {
std::list<MyObject> list;
for (int i = 0; i < 10; ++i) {
#if __cplusplus >= 201703L // C++17 allows more concise code
auto &obj = list.emplace_back(parent);
#else
auto &obj = (list.emplace_back(parent), list.back());
#endif
//...
}
return list;
}
int main() {
MyOtherObject other;
auto objects = makeObjects(&other, &other);
//...
objects.erase(objects.begin()); // the container manages lifetimes
//
}
C++ has strict object destruction order, and objects
is guaranteed to be destroyed before other
. Thus, by the time other.~QObject()
runs, there are no MyObject
children, and thus there's no issue with double-deletion.
In general, here are the viable approaches to storing QObject
collections, with their requirements:
std::array
- all elements of same type, fixed size, cannot be returned
std::list
- all elements of same type, doesn't have a RandomAccessIterator
, container owns the objects
std::deque
- all elements of same type, has RandomAccessIterator
but doesn't allow erase
since your objects are not MoveAssignable
(but of course can be cleared/destroyed), container owns the objects
std::vector<std::unique_ptr<BaseClass>>
- elements of any type i.e. it is a polymorphic container, container owns the objects
std::vector<QObject*>
or QObjectList
- non-owning, non-tracking container. Qt code is full of QObjectList = QList<QObject*>
.
QObject
(sic!) - elements of any QObject
type, container optionally owns the objects, container tracks the object lifetime, the element pointer can be used to remove the object from the container; only one container can hold a given object, uses a bare vector to store the objects and thus additions/removals of children are O(N)
.
When storing the objects themselves, and not their collections, with object lifetime being the same as the containing scope, it's easiest to keep them as values. For example:
class ButtonGrid : public QWidget {
static constexpr int const N = 3;
QGridLayout m_gridLayout{this};
QLabel m_label;
std::array<QPushButton, N*N> m_buttons;
public:
ButtonGrid(QWidget *parent = {}) : QWidget{parent} {
int r = 0, c = 0;
m_gridLayout.addWidget(&m_label, r, c, 1, N);
r ++;
for (auto &b : m_buttons) {
m_gridLayout.addWidget(&b, r, c);
c ++;
if (c == N)
c = 0, r ++;
}
}
};
Now, to answer your questions:
Should I better stick to Qt's object tree and ownership model? There's no choice in that matter: that model is there and it can't be disabled. But it's designed as a catch-all and you can preempt it. QObject
ownership model only ensures that children don't outlive the parent. It prevents resource leaks. You're free to end the childrens' lives before the parent dies. You're also free to have parentless objects.
What are the equivalent smart pointers in Qt? It doesn't matter. You're writing C++ - use std::shared_ptr
and std::unique_ptr
. There's no benefit to using Qt's equivalents since Qt 5.7: from that version onwards, Qt requires C++11 support in the compiler, and thus those pointers must be supported.
How to translate the above snippet into a version using smart pointers? There's no need to. Keep objects by value. Perhaps you need to have a different snippet that would actually require the use of smart pointers.
Should I change function signature into using smart pointers? No. You haven't motivated any smart pointer use.
N/A
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With