PDA

View Full Version : Struggling with signal which has a default parameter.



JPNaude
19th January 2011, 16:00
Hi

I'm really struggling with a signal which has a default parameter. Here is the way I declare the signal and slot:


// Signal declaration:
signals:
void layoutChanged(QObject* added_object = 0);

// Slot declaration:
public slots:
void rebuildTreeStructure(QObject* new_focus = 0);


And I connect it to a slot as follows:


connect(d_observer,SIGNAL(layoutChanged(QObject*)) ,SLOT(rebuildTreeStructure(QObject*)));


Then for whatever reason I emit the signal with a valid QObject reference and I always get a null in the slot. I've been trying to debug this for ages and found the following:

- Looking at the _moc file of the class emitting the signal I see that it calls it correctly and the _t1 parameter is valid during debugging:



// SIGNAL 7
void Qtilities::Core::Observer::layoutChanged(QObject * _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 7, _a);
}


Going through the stack I see that qt_metalcall is called with the wrong argument (or it looks like it). This function is called with _id = 8, which is the signal with its default parameter.



int Qtilities::Core::Observer::qt_metacall(QMetaObject ::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
case 0: modificationStateChanged((*reinterpret_cast< bool(*)>(_a[1]))); break;
case 1: monitoredPropertyChanged((*reinterpret_cast< const char*(*)>(_a[1])),(*reinterpret_cast< QList<QObject*>(*)>(_a[2]))); break;
case 2: monitoredPropertyChanged((*reinterpret_cast< const char*(*)>(_a[1]))); break;
case 3: propertyChangeFiltered((*reinterpret_cast< const char*(*)>(_a[1])),(*reinterpret_cast< QList<QObject*>(*)>(_a[2]))); break;
case 4: propertyChangeFiltered((*reinterpret_cast< const char*(*)>(_a[1]))); break;
case 5: numberOfSubjectsChanged((*reinterpret_cast< Observer::SubjectChangeIndication(*)>(_a[1])),(*reinterpret_cast< QList<QObject*>(*)>(_a[2]))); break;
case 6: numberOfSubjectsChanged((*reinterpret_cast< Observer::SubjectChangeIndication(*)>(_a[1]))); break;
case 7: layoutChanged((*reinterpret_cast< QObject*(*)>(_a[1]))); break;
case 8: layoutChanged(); break;
default: ;
}


Maybe I'm missing something simple. Any inputs will be appreciated.
Thanks,
Jaco

high_flyer
19th January 2011, 16:24
Did you clean/rebuild?

JPNaude
19th January 2011, 16:33
Hi

I've rebuilt it a few times. Have not cleaned but as far as I'm aware Creator cleans in any case during a rebuild. I've now made the slot's parameter not to have a default but that still does not solve anything. Also reading the "Signals And Slots With Default Arguments" part of the docs another time does not shed any light on this.

Any ideas?
Thanks,
Jaco

high_flyer
19th January 2011, 16:41
Then for whatever reason I emit the signal with a valid QObject reference and I always get a null in the slot.
Are you sure?
Make an assertion there (just before emitting), to be sure.

wysota
19th January 2011, 21:05
Show us the slot please.

JPNaude
20th January 2011, 06:54
high_flyer: Yes I'm 100% sure, stepped through it a few times already. I've added the assert like this:



Q_ASSERT(obj);
emit layoutChanged(obj);


Also obj is declared QObject*, its not a QPointer which might have become zero for some reason, although I'm sure this is not happening.

wysota: Here is the slot's declaration and implementation:


// The declaration:
private slots:
void rebuildTreeStructure(QObject* new_focus = 0);

// The implementation:
void Qtilities::CoreGui::ObserverTreeModel::rebuildTree Structure(QObject* new_focus) {
// Rebuild the tree structure:
QApplication::setOverrideCursor(QCursor(Qt::WaitCu rsor));
reset();
deleteRootItem();
QVector<QVariant> columns;
columns.push_back(QString("Child Count"));
columns.push_back(QString("Access"));
columns.push_back(QString("Type Info"));
columns.push_back(QString("Object Tree"));
d->rootItem = new ObserverTreeItem(0,0,columns);
d->rootItem->setObjectName("Root Item");
ObserverTreeItem* top_level_observer_item = new ObserverTreeItem(d_observer,d->rootItem);
d->rootItem->appendChild(top_level_observer_item);
setupChildData(top_level_observer_item);
//printStructure();
emit layoutAboutToBeChanged();
emit layoutChanged();

// Handle item selection after tree has been rebuilt:
if (new_focus) {
QList<QObject*> objects;
objects << new_focus;
emit selectObjects(objects);
} else if (d->selected_objects.count() > 0)
emit selectObjects(d->selected_objects);
else
emit selectObjects(QList<QObject*>());

QApplication::restoreOverrideCursor();
}


I'm wondering if it makes a difference that the slot is private since the signal and slot is on different objects. I did however changed it to public now and it did not work either however I'm still doing a rebuild all on it to double check.

Thanks,
Jaco

high_flyer
20th January 2011, 09:05
I'm wondering if it makes a difference that the slot is private since the signal and slot is on different objects
Yes, if the slot is private other classes signals can not access it.

From a brief look I see you are emitting layoutChanged() from your slot, and that one is with a default value, which would explain why in the debugger you see first the one with a non NULL object, and then one with NULL.
You have a recursion there.

Lykurg
20th January 2011, 09:31
Yes, if the slot is private other classes signals can not access it.
Unfortunately they can. Not direct, but through the meta object, it is possible. Unfortunately.
#include <QtGui>

class Test : public QWidget
{
Q_OBJECT
private Q_SLOTS:
void killAll()
{
qWarning() << Q_FUNC_INFO;
qApp->quit();
}
};


int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QTimer t;
t.setSingleShot(true);
t.setInterval(1000);

Test o;
QObject::connect(&t, SIGNAL(timeout()), &o, SLOT(killAll()));

// o.killAll(); fails
t.start();
return app.exec();
}

#include "main.moc"

high_flyer
20th January 2011, 09:36
// o.killAll(); fails
?
So if it fails it means the slot is not accessible... oder?

EDIT:
Oh, I get you now, you mean a direct call fails, but the signals/slot connection works.
Ok.

Thanks for the heads up!
Interesting...

Lykurg
20th January 2011, 09:45
Addendum: Thus you can also easily substitute the non working direct call with QMetaObject::invokeMethod() and break the encapsulation.

JPNaude
20th January 2011, 10:00
Ok, I've made the slot public and rebuilt. Still I get a null everytime.


From a brief look I see you are emitting layoutChanged() from your slot, and that one is with a default value, which would explain why in the debugger you see first the one with a non NULL object, and then one with NULL.
You have a recursion there.
I did mention it but probably not clear enough, the signal and slot are on different classes. Thus the layoutChanged() I emit in the slot is not the same, it is layoutChanged() on QAbstractItemModel.

high_flyer
20th January 2011, 10:10
the signal and slot are on different classes.
And the classes are also not related (through derivation) (just making sure)

JPNaude
20th January 2011, 10:17
Yes they are not related. I've checked that it only breaks once in that function as well.

As I said in my first post, it looks like the wrong signal is emitted by the meta object system.



int Qtilities::Core::Observer::qt_metacall(QMetaObject ::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
case 0: modificationStateChanged((*reinterpret_cast< bool(*)>(_a[1]))); break;
case 1: monitoredPropertyChanged((*reinterpret_cast< const char*(*)>(_a[1])),(*reinterpret_cast< QList<QObject*>(*)>(_a[2]))); break;
case 2: monitoredPropertyChanged((*reinterpret_cast< const char*(*)>(_a[1]))); break;
case 3: propertyChangeFiltered((*reinterpret_cast< const char*(*)>(_a[1])),(*reinterpret_cast< QList<QObject*>(*)>(_a[2]))); break;
case 4: propertyChangeFiltered((*reinterpret_cast< const char*(*)>(_a[1]))); break;
case 5: numberOfSubjectsChanged((*reinterpret_cast< Observer::SubjectChangeIndication(*)>(_a[1])),(*reinterpret_cast< QList<QObject*>(*)>(_a[2]))); break;
case 6: numberOfSubjectsChanged((*reinterpret_cast< Observer::SubjectChangeIndication(*)>(_a[1]))); break;
case 7: layoutChanged((*reinterpret_cast< QObject*(*)>(_a[1]))); break;
case 8: layoutChanged(); break;
default: ;
}


As I said, the debugger steps into case 8 in the above code so I'm pretty sure the problem is with the delivery of the signal, rather than the receiving side. At least it looks like that.

One thing I don't understand is the following:
The class emitting the signal is called Observer. The instance on which the signal is emitted is called TreeNode which inherits Observer. Stepping through the debugger the signal emission goes into the qt_metacall function of TreeNode and then basically calls qt_metacall on the Observer base class. This is the piece of code I'm referring to:



int Qtilities::CoreGui::TreeNode::qt_metacall(QMetaObj ect::Call _c, int _id, void **_a)
{
_id = Observer::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;


The above funciton is entered with _id = 12, however as soon as it steps into the base class observer the base class gets _id = 8 as the parameter which is the signal with the default argument. Maybe I don't know the moc system that well and its correct but it does not make sense to me.

high_flyer
20th January 2011, 10:34
The class emitting the signal is called Observer. The instance on which the signal is emitted is called TreeNode which inherits Observer.
I don't understand this - the instance should BE Objserver instance, not inherit it - which would mean its an instance of a class that inherits Observer - can you clarify this - since if this is confused, it can well be the root of the problem!

Lets try to do this in order:
What object (which class is it, what is its instance (variable/obejct) name?
What object declares and implements the slot?

based on your original post:

connect(d_observer,SIGNAL(layoutChanged(QObject*)) ,SLOT(rebuildTreeStructure(QObject*)));
The sender class and instance is also the slot class and instance, and in the slot you are emitting the same signal again, even though you said its another class...

wysota
20th January 2011, 10:48
Change the name of the signal and the slot to something not clashing with existing signals in Qt. You can also try declaring two separate slots - one with the QObject param and the other without it and see if it changes anything. It could be that moc has a bug somewhere that makes it work incorrectly with slots that have default params.

JPNaude
20th January 2011, 10:54
Ok no problem let me explain in more detail:



// This is a simplified view of the observer class which contains the signal. The signal is also emitted in one of this class's functions.
class Observer : public QObject
{
Q_OBJECT

public:
Observer();
virtual ~Observer();

void someFunctionEmittingTheSignal() {
QObject* new_obj = new QObject();
emit layoutChanged(new_obj);
}

signals:
void layoutChanged(QObject* added_object = 0);

}

// This is the class of which the instance is created:
class TreeNode: public Observer
{
Q_OBJECT

public:
TreeNode();
virtual ~TreeNode();
}


I have another class with the slot (again simplified):


// This is the class of which the instance is created:
class ObserverTreeModel: public QAbstractItemModel
{
Q_OBJECT

public:
ObserverTreeModel();
virtual ~ObserverTreeModel();
setObserverContext(Observer* obs) {
d_observer = obs;
connect(d_observer,SIGNAL(layoutChanged(QObject*)) ,SLOT(rebuildTreeStructure(QObject*)));
}

private slots:
void rebuildTreeStructure(Object* obj = 0) {
// Do some stuff that is going to change the layout of the tree.
emit layoutAboutToChange(); // Emits QAbstractItemModel::layoutAboutToChange();
emit layoutChanged(); // Emits QAbstractItemModel::layoutChanged();
}

private:
Observer* d_observer;
}


I do something like this then:


ObserverTreeModel model;
TreeNode tree_node;
model.setObserverContext(&tree_node);
tree_node.someFunctionEmittingTheSignal();


I hope that explains it better.


You can also try declaring two separate slots - one with the QObject param and the other without it and see if it changes anything. It could be that moc has a bug somewhere that makes it work incorrectly with slots that have default params.

I've removed the default parameter on the slots side, but it did not make a difference.

wysota
20th January 2011, 11:06
So the only explanation I can see is that your parameter has been deleted somewhere in the meantime. By the way, is line #15 of your last snippet a typo or a real code?

JPNaude
20th January 2011, 11:10
So the only explanation I can see is that your parameter has been deleted somewhere in the meantime. By the way, is line #15 of your last snippet a typo or a real code?

I'm very sure that its not deleted because I can access it in the tree view directly after this function. Also if it was a QPointer and it was deleted it would make sense but if it was deleted in my case I should get an invalid pointer.

Yes that was a typo, thanks.

high_flyer
20th January 2011, 11:16
private slots:
void rebuildTreeStructure(Object); //is this a typo? (QObject*)

EDIT:Oh, to slow with my post...

Well, try as wysota said to rename the signals and slots to be unique, see if this helps.

wysota
20th January 2011, 11:20
The problem is what you said in post #13 - QMetaObject is stepping into a wrong case. It should go into 7 and not 8. Can you show us all places where you emit this signal? In post #6 there is an emit layoutChanged() but it seems this is a different "layoutChanged()".

JPNaude
20th January 2011, 12:09
I've updated my example in post #16 to show what the other layoutChanged() signal is. It will make more sense now, they should be independent. I can try to change the signal names as you said to see if it makes any difference.

JPNaude
21st January 2011, 08:40
I figured it out...

Was my mistake. The observer class I mentioned is able to observe other observers creating a tree structure. The problem was that I emitted the signal in an observer low down in the tree and as the signal propagated through to the top observer (connected to the view) the parameter got lost since those connections were made without the parameters. I forgot that this is happening and thought I emitted the signal on the top level observer. My bad.

Thanks for your help and time again.
Cheers,
Jaco