乐趣区

翻译-为什么QObject子类不可复制

本文翻译自: https://www.cleanqt.io/blog/w…
原作者: 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 的人可以快速了解预期的用法: 不应 (也不能) 复制对象

退出移动版