简体   繁体   中英

Qt: How to use Qt's Smartpointers

I have experience in "old-fashioned" C++ programming (ie 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 , ie 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, eg

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:

  1. std::array - all elements of same type, fixed size, cannot be returned

  2. std::list - all elements of same type, doesn't have a RandomAccessIterator , container owns the objects

  3. 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

  4. std::vector<std::unique_ptr<BaseClass>> - elements of any type ie it is a polymorphic container, container owns the objects

  5. std::vector<QObject*> or QObjectList - non-owning, non-tracking container. Qt code is full of QObjectList = QList<QObject*> .

  6. 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:

  1. 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.

  2. 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.

  3. 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.

  4. Should I change function signature into using smart pointers? No. You haven't motivated any smart pointer use.

  5. N/A

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?

Yes. When using QObjects , I would recommend to rely on it's parent - child model to manage memory. It works well and you can't avoid it altogether, so use that.

2.) In C++ I can get by with std::shared_ptr and std::unique_ptr. What are the equivalent smart pointers in Qt?

There is QSharedPointer and QScopedPointer , which is somewhat similar to unique_ptr , but doesn't support move semantics. IMHO there is no reason to use these nowadays, just use the smart pointers provided by standard library (to manage lifetime of objects that do not derive from QObject ).

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?

Generally - use smart pointers only to manage memory = use them only when there is an ownership relation. If you just pass an instance to a function that works with it but doesn't take ownership, pass just a plain old pointer.

Concerning your example, it's a bit hard to say without context. Both ls and mi could be a unique_ptr . Better, you could just allocate ls on stack. And even better, avoid using QList and use std::vector or something like that.

  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?

Better to say "it depends". First, you should know what thread affinity in Qt is. Good example - QThread worker .

QObject uses parent-child memory management, so if the parent is destroyed, then all its children will be destroyed too. In the "Qt way", it is enough to manage only root object lifetime. It is very easy, when you create QObject -based classes on heap (for example, with new operator). But there are some exceptions:

  • You should be careful with objects that are created on the stack .
  • Classes with parent-child relations should belong to the same thread. You may use QObject::moveToThread() method to control thread affinity. So if instances of related objects should belong to different threads, they may not have parent-child relations.

    There is a pattern to auto-delete objects that belong to different threads. For example, if we should delete p2 when p1 is destroyed:

     QThread t; t.start(); QObject *p1 = new MyClass1{}; p1->moveToThread( &t ); p1->doSomeWork(); QObject *p2 = new MyClass2{}; QObject::connect( p1, &QObject::destroyed, p2, &QObject::deleteLater ); // here 

    Object p2 will be destroyed when you delete p1 .

    We've employed a useful method above: QObject::deleteLater() . It schedules object deletion asap. It may be used in several cases:

  • You need to delete an object from within a slot or during emitting of a signal. You should not delete such objects directly, because it may cause problems. Example: self-destroying button (somewhere in MyClass ):

     auto btn = new QPushButton{/*...*/}; QObject::connect( btn, &QPushButton::clicked, this, &MyClass::onClicked ); void MyClass::onClicked() { auto p = qobject_cast<QPushButton *>( sender() ); // delete p; // Will cause error, because it is forbidden to delete objects during signal/slot calls p->deleteLater(); // OK, deletion will occurs after exit from the slot and will be correct } 

For working with QObject-based pointer there is a QPointer helper. It will not automatically delete an object, but it will be valid all the time. It will contain a reference to an object or a nullptr . When object is removed, all its QPointer instances will be nulled automatically. Example:

class Foo : public QObject { /* ... */ };

class Bar
{
public:
    void setup( Foo * foo )
    {
        p = foo;
    }

    void use()
    {
        if ( p )
        {
            p->doSomething(); // Safe
        }
    }

private:
    QPointer<Foo> p;
};
// ...
Foo * foo = new Foo{};
Bar bar;
bar.setup( foo );
delete foo; // or not delete
bar.use();  // OK, it's safe

Note, that QPointer uses the QObject::destroyed() signal for managing internal reference, so using it to keep a large amount of objects may cause performance issues (only on mass creation/destruction). Performance with accessing to this pointers is same as with raw pointers.

  1. In C++ I can get by with std::shared_ptr and std::unique_ptr . What are the equivalent smart pointers in Qt?

Yes, there are QSharedPointer and QScopedPointer that work in similar way. Some benefits for using these pointers include custom deleters , like QScopedPointerDeleter , QScopedPointerArrayDeleter , QScopedPointerPodDeleter , QScopedPointerDeleteLater .

Also you may use something like this, for QObject-based classes, if you need postponed deletion:

QSharedPointer<MyObject>(new MyObject, &QObject::deleteLater);

Attention: you should not use this smart pointer with direct setting of parent objects, because destructor will be called twice.

class Foo : public QObject
{
QScopedPointer<MyClass> p;

public:
Foo()
    : p{ new MyClass(this) } // NO!!! Use new MyClass() without specifying parent
// Object lifetime is tracked by QScopedPointer
{}
};

If you have a list of raw pointers, you may use helpers like qDeleteAll() for cleanup. Example:

QList<MyObject *> list;
//...
qDeleteAll( list );
list.clear();

3.

4.

5.

It depends on your design and C++ code style. Personal approach, from my experience:

  1. Use parent-child only for QWidget -based classes.
  2. Use shared pointers only when you can't control an object lifetime
  3. In all other cases - use unique pointers
  4. To share something between two classes - use references .
  5. In case of complex logic - use smart pointers. But double-check for loops (use weak pointers in most cases)

Feel free to ask in comments for any clarifications.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM