如何在Qt中使函数暂时成为插槽

How to make a function to be a slot temporally in Qt?

本文关键字:插槽 函数 Qt      更新时间:2023-10-16

要使类的函数成为插槽,该类必须从QObject继承。但是,QObject 占用了相当多的内存。我不确定它是多少,以及内存是针对每个类还是每个对象。我的代码有许多小数据,其功能有时可能是一个插槽。我想知道是否有办法在使用时使类函数暂时成为插槽。使用后,插槽成本的内存将被删除。以下代码说明了该要求。

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().

如果你可以使用Qt5,你可以将信号连接到普通函数和静态方法(本质上是有趣的命名为普通函数):

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

其中actionQAction实例,SmallData::statF是静态SmallData方法。

根据 Christian Rau 的评论进行编辑,要调用特定实例,您还可以连接到 lambda:

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

在Qt4中,您可以使用QSignalMapper来实现大致相同的效果,但要多做一些对象。它允许您添加添加参数(在本例中,可能是vec的整数索引)到信号,基于哪个对象发出它。但在Qt4中,接收器仍然必须始终是QObject。

对于使用信号槽机制,你不会绕过QObject,但你可以做的是创建一个临时对象,该对象有一个调用你的函数的槽。您只需要注意正确释放对象即可。像这样:

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);
}

然后,您只需在每次需要时创建一个(或多或少临时的)Callback对象:

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

使用 oneShot 参数,您可以控制插槽在触发后是否应自动解散。

唯一的问题是,如果这个插槽从未被调用,那么你就会有一个泄漏的Callback挂在周围。为了适应这一点,你可以在parent参数中传递一些有意义的东西,以便Qt至少在以后的某个时间点关心正确的删除:

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

这样,您还可以将回调对象的生存期(以及信号槽连接)绑定到其他对象(例如,操作本身并在未选择任何项目时删除操作,或类似内容)。

或者,您也可以记住当前选定项目的Callback对象,并在删除后自行注意正确删除。

免责声明:请注意,上面的例子包含大量的 C++11,但我没有心情为 C++03 重写它。同样,这个解决方案也可以进一步改进,也许使用模板化函子而不是std::function(但如果我没记错的话,Qt 元对象系统不太喜欢模板)。


编辑:最后,Frank Osterfeld在他的评论中提出的解决方案可能比我上面过于通用的对象生命周期疯狂更简单:只需将操作连接到更高级别对象的单个插槽(您的主小部件或可能包含数据向量的项目模型),然后对当前选定的项目调用F

connect(action, SIGNAL(triggered()), this, SLOT(callF()));
...
void MyController::callF()
{
    treeView.selectedItem()->F();
}

我不认为你尝试做的事情在Qt中是不可能的。

如果你真的不想继承QObject,那么我建议你看看升压信号和插槽机制。