[Qt] Qt核心知识点

#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  ... 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值