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