3

To make a function of a class to be a slot, the class has to inherit from QObject. However, QObject takes up a quite large amount of memory. I am not sure how much it is and if the memory is for each class or each object. My code has many small data whose functions can be a slot sometime. I am wonder if there is a way to make a function of class to be a slot temporally when using it. After using it, the memory for the slot cost will be deleted. The following code illustrates the requirement.

class SmallData // size of 2 or 3 integers.
{
public:
    virtual void F(); // use it as a slot.
    virtual QMenu* createMenu(); // use this to create the context menu with
                                 // an action connected to F()
    ...
};

// use the small data
vector<SmallData> vec(1000000); // the vector is put at a tree view. When an
                                // item in the tree view is selected, a context
                                // menu pop up with an action to run F().
SmallData* data = treeView.selectedItem();    
connect(action, SIGNAL(triggered()), data, SLOT(F())); // How to make F() to be
                                                       // a slot just here.
                                                       // The action is from 
                                                       // data->createMenu().
user1899020
  • 13,167
  • 21
  • 79
  • 154
  • What makes you think QObject takes up a quite large amount of memory? – aschepler Feb 04 '13 at 15:52
  • I checked it but not very carefully. It has a parent pointer and other data to implement its mechanism. – user1899020 Feb 04 '13 at 15:56
  • 4
    That's not possible. Also, with a million items, using item-based models instead of a custom QAbstractItemModel will be your bottleneck, not just QObjects. Add a single slot, look up the selected SmallData objects, call F() on them. – Frank Osterfeld Feb 04 '13 at 15:57
  • 1
    @aschepler Well, it has all kinds of magical things, like a name, a parent, Qt's own RTTI, some flags. Those things will probably outweight this `SmallData` thing by some factor. Of course one might regard this as premature optimization. But then again a million such things isn't that insignificant an amount and we shouldn't prematurely allocate memory for stuff we never ever gonna need. – Christian Rau Feb 04 '13 at 16:15

3 Answers3

8

If you can use Qt5, you can connect signals to plain functions and static methods (which essentially are funnily named plain functions):

connect(action, &QAction::triggered, 
        &SmallData::statF); 

Where action is a QAction instance, and SmallData::statF is a static method of SmallData.

Edit per Christian Rau's comment, to call a particular instance, you can also connect to lambda:

connect(action, &QAction::triggered,
        [data]() { data->F(); });

Already with Qt4, you can use QSignalMapper to achieve much the same effect, with a few more objects. It allows you to add add a parameter (in this case, probably an integer index to your vec) to signal, based on which object emitted it. But in Qt4, receiver must still always be a QObject.

hyde
  • 60,639
  • 21
  • 115
  • 176
  • If this works, it will be the simplest solution. My SmallData::stateF is virtual and not static. Any way to do it? – user1899020 Feb 04 '13 at 16:21
  • 1
    +1 Wow, now that is nice (haven't had so many looks at the new Qt yet). Might be even used together with C++11's functional facilities to avoid static stuff: `connect(action, &QAction::triggered, std::bind(&SmallData::F, data));`. If this works (they at least speak about `tr1::bind` being supported) and you could add it, then that's the IMHO best answer available. – Christian Rau Feb 04 '13 at 16:22
  • @user1899020 Unfortunately method pointers are not supported (as far as I know). If you want a method of particular object instance to be called, it must be a QObject instance. QSignalMapper allows you to pass differenet parameter based on which QObject emitted the signal though, maybe that is of help to you. – hyde Feb 04 '13 at 16:29
  • 2
    @hyde See my comment, you can at least use `tr1::bind` (though I guess `std::bind` should work, too) or a capturing lambda (`connect(action, &QAction::triggered, [data]() { data->F(); });`), no need for any static rubbish. – Christian Rau Feb 04 '13 at 16:31
3

For using the signal slot mechanism, you won't get around QObject, but what you can do is create a temporary object that has a slot calling your function. You just have to care for properly releasing the object. Something like:

class Callback : public QObject
{
    Q_OBJECT

public:
    typedef std::function<void()> FunctionType;

    Callback(FunctionType fn, bool oneShot = true, QObject *parent = nullptr)
        : QObject(parent), fn_(std::move(fn)), oneShot_(oneShot) {}

public slots:
    void call()
    {
        fn_();        //delegate to callback
        if(oneShot_)
            deleteLater();  //not needed anymore
    }

private:
    FunctionType fn_;
    bool oneShot_;
};

Callback* makeCallback(FunctionType fn, bool oneShot = true, QObject *parent = nullptr)
{
    return new Callback(std::move(fn), oneShot, parent);
}

You then just create a (more or less temporary) Callback object each time needed:

SmallData* data = treeView.selectedItem();
connect(action, SIGNAL(triggered()), 
        makeCallback(std::bind(&SmallData::F, data)), SLOT(call()));

With the oneShot parameter you can control if the slot should dissolve automatically once triggered.

The only problem is, if this slot is never called, you have a leaking Callback hanging around. To accomodate this, you can pass something meaningful into the parent argument, so that Qt cares for proper deletion at least at some later point in time:

SmallData* data = treeView.selectedItem();
connect(action, SIGNAL(triggered()), 
        makeCallback(std::bind(&SmallData::F, data), true, this), SLOT(call()));

This way you can also bind the lifetime of the callback object (and thus the signal-slot connection) to some other object (e.g. the action itself and deleting the action when no item is selected, or something the like).

Alternatively, you can also remember the Callback object for the currently selected item and care for proper deletion yourself, once it's delesected.

disclaimer: Beware that the above example contains plenty of C++11, but I'm not in the mood to rewrite this for C++03. Likewise can this solution be imporved further, maybe using a templated functor instead of a std::function (but if I remember correctly the Qt meta object system doesn't like templates that much).


EDIT: In the end the solution proposed by Frank Osterfeld in his comment might be a much simpler approach for your situation than my overly generic object lifetime madness above: Just connect the action to a single slot of a higher level object (your main widget or maybe the item model containing the data vector) and call F on the currently selected item:

connect(action, SIGNAL(triggered()), this, SLOT(callF()));
...
void MyController::callF()
{
    treeView.selectedItem()->F();
}
Christian Rau
  • 45,360
  • 10
  • 108
  • 185
0

I don't think that what you try to do is possible in Qt.

If you really don't want to inherit QObject, then I suggest you have a look at the boost signals and slots mechanism.

PrisonMonkeys
  • 1,199
  • 1
  • 10
  • 20