翻譯 | 為什麼QObject子類不可複製?
- 2019 年 11 月 25 日
- 筆記
本文翻譯自: https://www.cleanqt.io/blog/why-qobject-subclasses-are-not-copyable 原作者: Alexander Fagrell 原文發布時間:2018年8月14日
如果您嘗試複製QObject派生的類,則會導致編譯器錯誤,例如:
class MyClass : public QObject { Q_OBJECT } my_class; auto my_class_copy = my_class;
使用Qt5並使用C++11(支援=delete
):
錯誤:使用已刪除的函數』MyClass::MyClass(const MyClass&)』
或更早版本:
錯誤:'QObject::QObject(const QObject&)'在此上下文中是私有的。
此行為是設計使然。但是為什麼要刪除複製構造函數(以及賦值運算符)?如果您仍要複製該怎麼辦?如果它不可複製,那麼它可以移動嗎?以下文章將研究這些問題,並探討在自定義子類中重複刪除操作是否是一種好習慣。就讓我們一探究竟吧!
不能複製QObject有幾個原因。其中兩個最大的原因是:
- QObjects之間通常使用訊號和槽機制進行通訊。不清楚連接的訊號和/或插槽是否應該轉移到副本。如果它們將被轉移,則意味著其他qobject將自動訂閱該副本。這很可能會給開發人員帶來混亂和不必要的副作用。
- QObjects被組織在對象樹中。通常一個QObject的一個實例有一個父對象和幾個子對象。在這個層次結構中副本應該組織在哪裡?孩子(和孫子……)也應該被複制嗎?
其他原因,但可能不那麼重要,是:
- 一個QObject可以被認為是唯一的,方法是給它一個可以用作參考鍵的名稱,即通過設置QObject::objectName()。如果設置了名稱,則不清楚應該為副本指定哪個名稱。
- QObjects可以在運行時使用新的屬性進行擴展。副本是否也應該繼承這些新屬性?
一般來說,QObjects是通過它們的指針地址被其他對象引用的。例如,前面提到的訊號和槽機制就是這種情況。因此,QObjects無法移動;他們之間的聯繫就會消失。在QObject的源程式碼中,我們可以看到沒有聲明move構造函數或move賦值運算符。但是,由於複製構造函數被刪除,所以不會隱式地生成move構造函數,如果開發人員試圖移動QObject,就會報編譯器錯誤。
因此,您不能複製,也不能移動QObject,但是如果要複製底層數據(或屬性)怎麼辦?Qt的文檔在Qt對象模型中區分了兩種對象類型:值對象和身份對象。值對象,如:QSize,QColor和QString是可被複制和分配的對象。相反,身份對象無法複製,但可以克隆。您可能已經猜到過,身份對象的一個示例是QOBject或從其派生的任何類。克隆的含義可以從官方文檔中讀取:
克隆意味著創建一個新的身份,而不是舊身份的完全副本。例如,雙胞胎有不同的身份。他們可能看起來一樣,但是他們有不同的名字,不同的地點,可能有完全不同的社交網路。
我對克隆的理解是,你可以在一個子類中暴露一個clone()函數,它創建了一個新的身份,但不是一個真正的副本,即:
class MyClass : public QObject { Q_OBJECT public: MyClass* clone() { auto copy = new MyClass; //copy only data return copy; } private: //data }; ... auto my_class = new MyClass; auto my_class_clone = my_class->clone();
雖然這是可能做到的,但我不建議這樣做。這可能會導致不必要的副作用,因為Qt開發人員很可能對QObject有一些假設。如果您需要創建一個克隆,我建議您查看一下您的總體設計和體系結構。也許數據可以解耦或分解?
Q_DISABLE_COPY(Class)在子類中重複
在stackoverflow帖子建議總是在你自己的類中重新聲明宏Q_DISABLE_COPY(Class),即:
class MyClass : public QObject { Q_OBJECT Q_DISABLE_COPY(MyClass) // See macro below public: QObject() {} };
註:(stackoverflow帖子https://stackoverflow.com/questions/19854371/repeating-q-disable-copy-in-qobject-derived-classes
)
#define Q_DISABLE_COPY(Class) Class(const Class &); Class &operator=(const Class &);
正如stackoverflow帖子中所述,主要原因是為了**改善錯誤資訊*。如果沒有宏,則使用Qt4報告以下錯誤資訊:
錯誤:'QObject::QObject(const QObject&)'在此上下文中是私有的。
使用宏,將會報以下錯誤資訊:
錯誤:'MyClass::MyClass (const MyClass&)'在此上下文中是私有的。
對於Qt的新手來說,最後一條錯誤消息要容易得多。
但是從Qt5開始,宏被更改並聲明為:
#ifdef Q_COMPILER_DELETE_MEMBERS # define Q_DECL_EQ_DELETE = delete #else # define Q_DECL_EQ_DELETE #endif #define Q_DISABLE_COPY(Class) Class(const Class &) Q_DECL_EQ_DELETE; Class &operator=(const Class &) Q_DECL_EQ_DELETE;
不在子類中添加宏,則顯示以下錯誤消息:
錯誤:使用已刪除的函數』MyClass::MyClass (const MyClass&)』。
複製構造函數和賦值操作符使用=delete
聲明,而不再是聲明私有,從而產生了一個首選的錯誤消息。
即使錯誤消息已得到改善,我仍然相信在派生類中重新聲明宏是有價值的,因為它記錄了類的行為。剛接觸Qt的人可以快速理解其用法:不應(也不能)複製對象!