Qt如何设计接口强制通过signal/slot调用slot 禁止直接调用
完全不需要靠开发者自觉,从编译期到运行时有多层硬约束手段,可以从接口设计层面直接堵死非法直接调用的可能,根本不给不规范写法留空间。
编译期权限拦截:把执行逻辑彻底藏到私有作用域
这是成本最低、效果最好的方案:把所有重负载任务的执行函数,全部放到Worker类的private或者private Q_SLOTS下,对外公开的头文件里,不要声明任何公有的、可以直接触发任务执行的成员函数。
这里要注意一个Qt的特性:元对象系统触发槽函数不受C++访问控制限制,哪怕槽是私有的,只要通过QObject::connect绑定的信号触发,执行逻辑完全正常,不会报权限错误。Qt5以后支持的新式函数指针connect语法,甚至可以直接连接到非slot的私有成员函数,你连slot标记都不用加,外部代码根本感知不到执行函数的存在,编译阶段就会把所有直接调用的写法拦下来。
举个最简单的声明例子:// heavyworker.h 对外暴露的头文件 class HeavyWorker : public QObject { Q_OBJECT public: explicit HeavyWorker(QObject *parent = nullptr); // 这里没有任何公有的doXxxTask之类的执行函数 signals: // 可以对外暴露任务完成、进度通知的信号 void taskProgress(int percent); void taskFinished(const Result &res); private: // 所有实际执行逻辑全放私有区,外部根本看不到 void doLongRunningTask(const Param &p); void doAnotherCalcTask(); };外部拿到
HeavyWorker的实例时,根本找不到可以直接调用的任务执行入口,除了走connect绑定信号触发之外,没有别的调用路径。运行时强校验,非法调用直接中断
如果因为历史原因,已经有部分执行函数被设成了公有,可以在每个任务执行函数的最开头加线程归属校验:如果当前执行线程不是Worker对象绑定的线程,直接触发断言中断程序,第一时间把不规范的调用打出来。
示例代码:void HeavyWorker::doLongRunningTask(const Param &p) { Q_ASSERT_X(QThread::currentThread() == this->thread(), "HeavyWorker::doLongRunningTask", "非法调用:禁止直接调用执行函数,请通过signal/slot机制投递任务"); // 以下是实际业务逻辑 }Debug模式下只要有人直接调用这个函数,程序会立刻中断并定位到调用位置,开发者根本没法蒙混过关;Release模式下也可以加判断,发现线程不匹配就直接返回、打错误日志,避免逻辑跑到错误线程引发诡异问题。
PIMPL模式彻底隔离实现
如果要做更严格的隔离,可以用Qt常规的d指针(PIMPL)写法:对外公开的HeavyWorker类只保留信号、必要的状态查询接口,所有任务执行逻辑全部放到私有的内部类HeavyWorkerPrivate里,内部类和对外公开类绑定到同一个工作线程。外部代码连执行函数的声明都看不到,从二进制层面就断绝了直接调用的可能。代理模式锁死调用路径
最严格的场景下,可以把HeavyWorker的构造函数设为私有,只提供静态工厂方法,工厂方法不直接返回Worker实例,而是返回一个只包含任务触发信号的代理对象。调用方拿到代理对象之后,除了发信号之外没有任何办法触发任务执行,连Worker实例本身都接触不到,从根源上避免直接调用。
很多人觉得这类规范只能靠自觉,本质是写代码时习惯把所有槽函数都设为public,主动给外部开了直接调用的口子——这不是Qt的signal/slot机制做不到强制约束,是接口设计本身没有做权限收敛。就像你把类的内部成员变量全设成public,自然没法阻止别人随便修改内部状态,和语言、框架本身没有关系。
内容的提问来源于stack exchange,提问作者KcFnMi

