Trinopoty
Trinopoty

Reputation: 423

Qt container item ownership?

Who owns the items in a Qt collection such as QList or QContiguousCache? Also, Qt collections seem to take a reference instead of a pointer, does it internally clone the object using copy constructor or do I need to make sure the object is not destroyed?

I've been getting weird behavior with QContiguousCache and I can't seem to find the ownership information anywhere. If it helps, I'm trying to put some QString into the QContiguousCache from within one function and access them in another. I'm constantly getting null pointers in this case.

Upvotes: 2

Views: 511

Answers (1)

Scheff's Cat
Scheff's Cat

Reputation: 20141

Qt containers are comparable to std containers – they just store values (which might be QObject* or QWidget*) but they don't take ownership in the sense of how Qt manages ownership (e.g. for child widgets of widgets).

That said, I had a look into the doc. of QList to understrike this:

A common requirement is to remove an item from a list and do something with it. For this, QList provides takeAt(), takeFirst(), and takeLast(). Here's a loop that removes the items from a list one at a time and calls delete on them:

QList<QWidget *> list;
...
while (!list.isEmpty())
  delete list.takeFirst();

Though, mentioning takeAt() made me uncertain for a second. takeAt() sounds like “stealing ownership”. Could I be wrong?

Hence, I made a small sample to illustrate the issue:

For this, I made a thin-wrapper Label for QLabel to report construction and destruction.

testQListOwnership.cc:

// Qt header:
#include <QtWidgets>

struct Label: public QLabel {
  Label(const QString &text): QLabel(text)
  {
    qDebug() << "Label::Label(" << text << ")";
  }
  virtual ~Label()
  {
    qDebug() << "Label::~Label(" << text() << ")";
  }
  Label(const Label&) = delete;
  Label& operator=(const Label&) = delete;
};

// main application
int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // setup GUI
  QWidget qWinMain;
  qWinMain.setWindowTitle("Test QList Ownership");
  QVBoxLayout qVBox;
  QList<QLabel*> pQLbls;
  for (int i = 1; i <= 3; ++i) {
    QLabel *pQLbl = new Label(QString("Label ") + QString().number(i));
    qDebug() << "pQLbls.append(pQLbl);";
    pQLbls.append(pQLbl);
    qDebug() << "Label(" << pQLbl->text() << ").parent():" << pQLbl->parent();
    qDebug() << "qVBox.addWidget(pQLbl);";
    qVBox.addWidget(pQLbl);
    qDebug() << "Label(" << pQLbl->text() << ").parent():" << pQLbl->parent();
  }
  qDebug() << "&qWinMain:" << &qWinMain;
  qDebug() << "qWinMain.setLayout(&qVBox);";
  qWinMain.setLayout(&qVBox);
  for (QLabel *pQLbl : pQLbls) {
    qDebug() << "Label(" << pQLbl->text() << ").parent():" << pQLbl->parent();
  }
  QPushButton qBtnRemoveLabels("Remove Labels");
  qVBox.addWidget(&qBtnRemoveLabels);
  qWinMain.show();
  // install signal handlers
  QObject::connect(&qBtnRemoveLabels, &QPushButton::clicked,
    [&]() {
      for (QLabel *pQLbl : pQLbls) {
        qDebug() << "pQLbl->setParent(nullptr);";
        pQLbl->setParent(nullptr);
        qDebug() << "Label(" << pQLbl->text() << ").parent():" << pQLbl->parent();
      }
      qDebug() << "pQLbls.clear();";
      pQLbls.clear();
    });
  // runtime loop
  return app.exec();
}

and the Qt project to build testQListOwnership.pro:

SOURCES = testQListOwnership.cc

QT += widgets

Output:

Qt Version: 5.13.0
Label::Label( "Label 1" )
pQLbls.append(pQLbl);
Label( "Label 1" ).parent(): QObject(0x0)
qVBox.addWidget(pQLbl);
Label( "Label 1" ).parent(): QObject(0x0)
Label::Label( "Label 2" )
pQLbls.append(pQLbl);
Label( "Label 2" ).parent(): QObject(0x0)
qVBox.addWidget(pQLbl);
Label( "Label 2" ).parent(): QObject(0x0)
Label::Label( "Label 3" )
pQLbls.append(pQLbl);
Label( "Label 3" ).parent(): QObject(0x0)
qVBox.addWidget(pQLbl);
Label( "Label 3" ).parent(): QObject(0x0)
&qWinMain: QWidget(0x2332b5f818)
qWinMain.setLayout(&qVBox);
Label( "Label 1" ).parent(): QWidget(0x2332b5f818)
Label( "Label 2" ).parent(): QWidget(0x2332b5f818)
Label( "Label 3" ).parent(): QWidget(0x2332b5f818)
QWindowsWindow::setGeometry: Unable to set geometry 102x102+960+460 on QWidgetWindow/'QWidgetClassWindow'. Resulting geometry:  120x102+960+460 (frame: 8, 31, 8, 8, custom margin: 0, 0, 0, 0, minimum size: 102x102, maximum size: 16777215x16777215).

Snapshot of testQListOwnership

The QList<QLabel*> pQLbls doesn't take ownership.

Even the QVBoxLayout qVBox doesn't take ownership.

Ownership is taken by QWidget qWinMain after the qVBox is set as layout of it.

Output after clicking on ×:

Label::~Label( "Label 1" )
Label::~Label( "Label 2" )
Label::~Label( "Label 3" )

The QWidget qWinMain ensured that the Label instances are deleted when it is destroyed itself.

Now, what happens when the Labels are “stolen” from qWinMain:

Output after clicking on Remove Labels:

pQLbl->setParent(nullptr);
Label( "Label 1" ).parent(): QObject(0x0)
pQLbl->setParent(nullptr);
Label( "Label 2" ).parent(): QObject(0x0)
pQLbl->setParent(nullptr);
Label( "Label 3" ).parent(): QObject(0x0)
pQLbls.clear();

Oops! By clicking on Remove Labels, I produced some memory leaks.

Snapshot of testQListOwnership (after clicking on button “Remove Labels”)

Output after clicking on ×:


… in words: nothing.

Memory of leaked Label instances is surely freed by OS (like anything else) but no proper destruction happens anymore.


Further reading (found when searching the Web):

Understand the Qt containers

Upvotes: 1

Related Questions