PDA

View Full Version : Deleting from TreeWidget... Memoryleak



VireX
2nd May 2007, 23:06
I know it deletes automatically any QObjects but for some reason the delete of QTreeWidgetItems causes problems.

What happens with the following code is, I use SafeDeleteItem to delete a QTreeWidgetItem or a USER from the list. However, sometimes when users are deleted from the list more and more of them begin to exist (because we dont delete them live, if we do use delete pItem it will CRASH the program).

I mean pretend I click CreateOwnerGroup button, and suddenly a group is created, and then pretend I click DeleteOwnerGroup Button... Well now the group I just made is GONE. However, since we didn't use delete... If I press Create Delete Create Delete Create Delete like 50 times, i'll have like 50 MBs of more memory in the processlist because the TreeWidgets are never deleted, they just disappear, but more and more memory adds up, and pretty soon if a user abuses this they can crash their comp because of too much memory use (its a memory leak).

Can anyone help me properly delete?
I do not understand why Qt doesn't have a simple function like this:
QTreeWidget->deleteRow(QTreeWidgetItem*);

Example is like you would delete using the following code:


static void SafeDeleteItem( QTreeWidgetItem *pItem )
{
QTreeWidgetItem *pParent = pItem->parent();
assert( pParent );
int idx = pParent->indexOfChild( pItem );
pParent->takeChild( idx );
//delete pItem; // mem leak WTF! FIXME
}

void Cclass::DeleteUser( int id, bool wasOwner)
{
QString qsDisplayName = m_pMainClass->GetUserInfo(id).DisplayName();
QString qsChatMsg(QString("<span style=\"color: #858585;\">&lt;%1 has been deleted from list&gt;</span>").arg(qsDisplayName));
echoLog(qsChatMsg);

if(wasOwner){ // if the user was the owner of the list, then move the users up
// delete MapItems[id]; // delete first from the std::map!!!
SafeDeleteItem(MapItems[id]);
//MapItems.erase(id); // remove old item
LOG("a");
// delete MapItems[GetOwner()]; // remove old item for the new host
SafeDeleteItem(MapItems[GetOwner()]);
LOG("b");
MapItems[GetOwner()] = new QTreeWidgetItem(OwnerTopLevelNode); // spawn new item for the new Owner
MapItems[GetOwner()]->setText(0, m_pMainClass->GetUserInfo(GetOwner()).DisplayName());
LOG("c");
}
else
{
// If they weren't owner just delete their item
// delete MapItems[id];
SafeDeleteItem(MapItems[id]);

MapItems.erase(id);
}
}


Basically this code

wysota
3rd May 2007, 15:48
You can't delete objects which are arguments to the slot you want the delete to take place in, because it will confuse other slots that may be connected to the same signal. Instead you can use a 0 timeout timer that will fire after all the slots and there you can perform the deletion.


QQueue<QTreeWidgetItem*> delqueue;
void X::someSlot(QTreeWidgetItem *pItem){
QTreeWidgetItem *pParent = pItem->parent();
int idx = pParent->indexOfChild( pItem );
pParent->takeChild( idx );
delqueue.enqueue(pItem);
QTimer::singleShot(0, this, SLOT(deleteWaiting()));
}
void X::deleteWaiting(){
while(!delqueue.isEmpty())
delete delqueue.dequeue();
}

VireX
1st June 2007, 04:09
It's not a slot or a signal though. It has nothing to do with either. It's not connected to anything.

Let me show you a more detailed code for that, this is updated with logs so we can see better, there must be a bug in Qt or something because this makes no sense:


inline static void SafeDeleteItem( QTreeWidgetItem *pItem )
{
QTreeWidgetItem *pParent = (QTreeWidgetItem*)pItem->parent();
LOG("SafeDelete Text: %s (%x)", Q2A( pItem->text( 0 ) ), pItem );
assert( pParent );
int idx = pParent->indexOfChild( pItem );
LOG("index=%d; ParentText: %s",idx, Q2A( pParent->text(0) ));
LOG("Can We access pItem? %s %s", Q2A( pItem->text(0) ), Q2A(pItem->parent()->text(0)));
pParent->takeChild( idx );
LOG("Can We access Parent? %s", Q2A( pParent->text(0) ));
LOG("Can We access pItem? %s %s", Q2A( pItem->text(0) ), Q2A(pItem->parent()->text(0)));

ASSERT( !pItem->parent() );
ASSERT( pParent->indexOfChild ( pItem ) == -1 );
ASSERT( !pItem->treeWidget() );
LOG("parent=%d",pParent->treeWidget() );
ASSERT( pParent->treeWidget() );

delete pItem; // mem leak

LOG("SafeDeleteOut" );

}


OUTPUT in log:

22:55:23 SafeDelete Text: bob (2e25d20)
22:55:23 index=1; ParentText: CorrectParentText
22:55:23 Can We access pItem? bob CorrectParentText
22:55:23 Can We access Parent? CorrectParentText
22:55:23 Can We access pItem? bob CorrectParentText
22:55:23 Debug assert failed on line 239 of testone.h.

How can I access pItem->parent()->text(0), but then, the next line assert fails ON:
ASSERT(!pItem->parent())

I don't understand this.

EDIT:
If I remove the asserts, it goes by this function OUT... and then it comes back to this function again in the next item in the list... and crashes on the SECOND
LOG("Can We access pItem? %s %s", Q2A( pItem->text(0) ), Q2A(pItem->parent()->text(0)));

Like as if takeChild destroys the parent or something.

wysota
1st June 2007, 05:53
What calls that "DeleteUser" method? How does the structure of the tree look like? Do those items really have parent items?

VireX
1st June 2007, 18:55
As you can see by the log data:

22:55:23 SafeDelete Text: bob (2e25d20)
22:55:23 index=1; ParentText: CorrectParentText
22:55:23 Can We access pItem? bob CorrectParentText
22:55:23 Can We access Parent? CorrectParentText
22:55:23 Can We access pItem? bob CorrectParentText
22:55:23 Debug assert failed on line 239 of testone.h.

This means, that bob has a parent named CorrectParentText, and so I don't understand how an assert can fail, when it calls the parent() even though we just did it the line before, unless its some sort of weird bug in takeChild causing memory corruption or something.

what the last two lines mean is this:
pItem->parent()->text( 0 ); <--- no crash.
pItem->parent() <--- crash.

wysota
1st June 2007, 21:33
Correct me if I'm wrong but it seems to me the behaviour is perfectly fine with what your code looks like. The thing you do is more or less like the following:
1. find index of pItem which is child of pParent
2. pParent->takeItem(pItem)
3. access pItem->parent()
4. app asserts

The thing is that in (2) you remove the item from its parent, thus it doesn't have a parent anymore, therefore accessing the parent before (2) is fine, but after (2) causes the assert to fire because the parent is not valid anymore (it's null).