#1 本征 和 值
所有继承自QObject的类的实例,都会在元对象系统中注册自己,他们除了具备c++类的基本特征外,还具备
Qt特有的特征,即本征。
所谓的本征是此对象在元对象注册信息的合集,比如:
1)当前对象和其他对象的信号/槽链接关系;
2)每个对象都有的objectname
3)此对象在元对象系统的 “对象树” 中的位置
4)运行时,属于对象的附加属性(qt可以为对象创建动态属性,c++语法不支持,c++中的类都是静态属性,
如果想增加属性只能派生子类)
两个对象即便具备完全基于类层面的相同的属性。但是他们在元对象系统中也是完全不一样的。
(!!!)因此,在qt中所有继承自QObject的类都没有 “拷贝构造函数 和 赋值构造函数”,即
QObject A;
QObject B(A); //错误
QObject C=A; //错误
因为在Qt中,对象不在是独立的个体,而是存在于元对象 体系 中,是有 “社会关系的” , 因此拷贝 和 赋值
这样的动作会导致二义性,因此Qt索性放弃这两个操作。
(!!!)像QString这样的基础类,就没有继承QObject,因此其提供了拷贝构造 和 赋值构造,因此可以做
为函数的参数进行拷贝构造的 “值传递”。
像QTimer就继承自QObject,如果像传递QTimer给函数,就必须使用指针/引用进行传递。
(!!!)也正是这点,Qt的所有容器内,都 “不能” 存放 QObject子类的实例,只能存放指针,但是可以存放
没继承QObject的类的实例,比如;
QMap<QString,QString> map1 ; //正确
QMap<QString,QTimer> map2 ; //错误
QMap<QString,QTimer*> map3; //正确
For example, without a copy constructor, you can't use a subclass of QObject as the value to be
stored in one of the container classes. """"You must store pointers.""""
#2 信号 和 槽
QObject有一个虚函数 void QObject::connectNotify(const QMetaMethod &signal)
当有信号链接到 当前对象时,此虚函数会被调用,具体的信号名为 signal , 即:
class A:public QObject
{
signal:
void someoneconnecttome();
public:
void connectNotify(const QMetaMethod &signal)override
{
//如果有其他对象connect这个实例,则此函数会被调用,具体的connect的信号
//名为signal
if (signal == QMetaMethod::fromSignal(A::someoneconnecttome())) {
//如果connect的是someoneconnecttome信号,那么进入此流程
}
}
}
同理,disconnectNotify()为diconnect当前对象的某个信号时会被调用的虚函数。
connect : 信号链接
disconnect :信号断开
blockSignals : 信号丢弃 (所有由本对象发出的信号都被丢弃,不会被缓存,不会被传递给
connect到这个对象任意信号的槽),就是把对象自己的信号功能废除,但是
自己的槽依旧可以接收信号,只是自己不能对外发信号。
PS:destory信号不会被block
#3 对象树
QObject们会自发组织一个大的树形结构,每个QObject和QObject子类的对象都有一个QObject的parent(可以指定
这个parent为0来脱离对象树,这里不讨论),当指定了parent以后,对象就被挂载parent的children树下面,
(!!!)如果parent析构了,那么parent的所有children都会被析构。
(!!!)这在界面编程中很有用,但是在数据对象中需要格外注意,这是潜在的非法内存访问崩溃点。
PS : QObject的成员变量不会自动成为这个对象的children,除非自己手动设置。
#4
每个QObject和继承自QObject的对象,都有一个objectName函数,用来获取对象在元对象系统中的名字。
可以通过metaObject() 来获取对象注册在元对象系统中的所有信息(元对象类实例,相当于元对象系统为每个QObject
对象都额外创建了一个元对象实例,用来管理元对象信息,类似于 “组合” 的意思)。在这个元对象实例中,可以
查询N多信息,比如类名,继承关系,等等。
比如继承关系可以通过 inherits()校验。
#5
每个QObejct和继承自QObject的类的实例在销毁(不是析构)之前都会发出destroyed(QObject *obj = nullptr)信号,
在这个信号发送之后,会先销毁其所有children ,然后销毁自己。
可以看到这个信号会把对象的指针赋默认值为空,我们也可以自行捕捉,然后避免野指针的出现。
#6
QObject提供虚函数bool QObject::event(QEvent *e)
所有发送给QObject和QObject子类对象的事件都会进入这个虚函数,入参就是发生的事件类型,可以通过重载
这个虚函数来为不同的事件指定不同的处理流程。比如下面:
class MyClass : public QWidget
{
Q_OBJECT
public:
MyClass(QWidget *parent = 0);
~MyClass();
bool event(QEvent* ev) override
{
if (ev->type() == QEvent::PolishRequest) { //如果接收到PolishRequest事件
doThings(); //先进行自己定义的处理流程
return true; //不在调用父类的事件处理流程,直接截断
} else if (ev->type() == QEvent::Show) { //如果是Show事件
doThings2(); //先进行自己定义的处理流程
QWidget::event(ev); //再调用父类的Show事件处理流程
return true; //返回结束
}
// Make sure the rest of events are handled
return QWidget::event(ev); //如果既不是PolishRequest,又不是Show
//则调用父类的事件处理流程
}
};
PS : 什么是事件,事件就是默认的一套信号,某个对象触发了什么事件,等于说某个信号被发送给了某个对象。
区别在于,事件的数量是有限的,信号可以自定义无限添加。
信号 - 槽
事件 - 事件处理程序
信号排队,事件不排队
#7
如果使用信号/槽/属性,那么Q_OBJECT宏是强制的(mandatory).
所有的QWidget都继承QObject,可以通过QObject::isWidgetType()来判断一个QOBject是不是QWidget类。
#8 QObject 和 线程的关系
Qt中,QObject对象 和 线程有一个 内在的关系,目前不确定这个关系是怎么形成的,可能是由于元对象系统的副作用。从计算机层面来说,对象 是从属于内存层面(new的在堆中,其他在栈中),线程从属于 执行层面(代码正文区),本不相关,但是Qt 的 QObject 和 元对象系统让这两者有了一定的关系。一个对象可以属于一个线程,也可以不属于任何线程,跨线程操作 堆 里的对象有一定的约束性,但是操作栈里的对象依旧是不能跨线程的。
这就是 Qt的 Thread affinity
通过QOject::thread()来获取此对象所处的线程id,如果返回0,则表示此对象不归属于任何线程,此时这个对象的所有信号槽和事件机制都将失效。它不会收到任何信号,也不会收到任何事件通知。
QObject 只生存再 创建它的那个线程中,而且,如果这个QObject有信号处理函数和事件处理函数,此QObject在收到信号和事件通知时,相应的处理函数也是在这个线程中执行的。
(!!!)如果QObject有存在的parent,则它必须和parent在同一个线程中:
1)如果有OQBject A 和 QObject B,想让B做A的parent,那么这两个对象必须在同一个线程中,
否则A.setParent(&B)失败
2)当一个QObject 被挪到其他线程中,他的所有children都会被一并挪过去
3)基于1)和2),如果一个QObject像移动到其他线程中,那么他必须没有parent,否则将违背1)和2)
4)QThread::run()会另起一个线程执行,而在run中创建的QOBject都属于run所属的线程,因此不能将他们的parent设置为启动run的那个QThread实例,因为这个QThread实例属于创建它的线程,和run不在一个线程。
在#3 对象树中有提到:
-------QObject的成员变量不会自动成为这个对象的children,除非自己手动设置。--------
但是QObject对象和他的成员变量默认同属于一个线程。因此为了防止在使用QObject::moveToThread时出现
QObject移动到新线程,而成员变量还留在旧线程,我们在创建成员变量或者在构造OQBject的时候,一定要将
成员变量的parent指定为this,即所属对象的实例。Note: A QObject's member variables do not automatically become its children. The parent-child
relationship must be set by either passing a pointer to the child's constructor, or by calling
setParent(). Without this step, the object's member variables will remain in the old thread
when moveToThread() is called.小结:线程 和 QObject对象本是两个独立概念,由于元对象系统的存在,让这两个概念有着千丝万缕的关系。
比如:“不能在线程A 中删除属于线程B的QObject对象,即便A和B线程都可以访问到处在堆里QObject对象”当然,
非OQbject对象就没有这个限制了,因此需要弄清楚类是否继承自QObject。
我们在编程时,需要时刻注意QObject属于哪个线程(使用thread()函数判断),避免跨线程处理不属于自己线程的QObject对象。
如果需要跨线程操作对象,务必使用信号/槽进行消息传递,信号/槽机制可以跨线程工作。
目前已知,不能跨线程销毁,QTimer不能跨线程stop。
(*)非QOjbect对象遵循c++准则,new出来的都在堆里面,可以跨线程访问,但是QObject对象需要接受
Qt的约束,不能自由地跨线程操作,如果确实想操作,可以先把对象moveToThread,然后再操作做,
比如,有QMap<QString,QTimer*> map_timer ,如果多个线程都想操作Map中的Timer,那需要先通过
QMap::value(key)获取QTimer的指针,然后moveToThread到创建QTimer的线程中,然后再操作即可。
#9
moveToThread()
If targetThread is nullptr, all event processing for this object and its children stops, as they are
no longer associated with any thread.
如果一个QObject不属于任何线程,那么它的一切信号和事件都将立刻不工作。
QObject切换线程后,它的所有定时器都将reset,先在原先线程中停止,再在新线程中启动。
QObject切换线程时,会发送一个 QEvent::ThreadChange 事件。
使用限制:
1)可以从当前OQBject所属线程移动到其他线程
2)不能在其他线程中,调用不属于这个线程的QObject的moveToThread方法,以达到把不属于这个线程的
QObject拉到本线程中的目的
3)如果QObject不属于任何线程(thread()返回0),那么可以在任何线程中调用这个QObject对象的moveToThread
方法把它拉到执行线程中。
#10 Property Documentation ...