天天看點

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

#10         Property Documentation  ...