Qt_Q_DISABLE_COPY

头图

​ 今天在抄代码的时候,发现了一个新的东西,Q_DISBALE_Copy,用了这么多多年Qt还,还第一次遇到这个真实惭愧,赶紧学习记录一下。


关键字: QtQ_DISABLE_COPYQObject拷贝不可赋值

Q_DISABLE_COPY

1 官方说法

​ 这里先看看Qt帮助文档中是怎么描述的

Disables the use of copy constructors and assignment operators for the given Class.

Instances of subclasses of QObject should not be thought of as values that can be copied or assigned, but as unique identities. This means that when you create your own subclass of QObject (director or indirect), you should not give it a copy constructor or an assignment operator. However, it may not enough to simply omit them from your class, because, if you mistakenly write some code that requires a copy constructor or an assignment operator (it’s easy to do), your compiler will thoughtfully create it for you. You must do more.

粗暴翻译一下:来自有道

禁止对给定类使用拷贝构造函数和赋值操作符

每一个继承了QObject的子类都不应该被看做是可以复制或者赋值的,而应该看做事唯一的标识。这意味着当你创建自己的子类时,你应该不给他一个复制构造函数或复制操作符,然而,从类中简单地省略他们是不够的,因为如果您错误的编写了一些需要复制构造函数或者赋值操作的代码,编译器将为你创建,你必须做的更多。

所以,我们会看到,当一个类继承至QObject的时候,就会看到这样的宏

  class MyClass : public QObject
  {
  private:
      Q_DISABLE_COPY(MyClass)
  };

其实这个宏展开就是下面的样子

  class MyClass : public QObject
  {
  private:
      MyClass(const MyClass &) = delete;
      MyClass &operator=(const MyClass &) = delete;
  };

即便如此,也不能完全使用与所有情况,你可能会这样做:

  QWidget w = QWidget();

首先,不要这样做,大多数编译器会生成使用拷贝构造函数的代码,因此会报告隐私违反错误,但C++编译器不需要以特定的方式为该语句生成代码,他既不能使用拷贝构造函数也不能使用私有的复制操作符生成代码。在这种情况下,不会报告错误,但是在调用w的成员函数是,应用程序可能会崩溃。

2 民间解释

搬运链接:https://www.bbsmax.com/A/E35p6OOKzv/

QObject中没有提供一个拷贝构造函数和赋值操作符给外界使用,其实拷贝构造函数和赋值操作符都是已经声明了的,但是他们被使用了**Q_DISABLE_COPY()**宏放在了private区域,因此所有继承子QObject的类都使用这个宏声明了他们的拷贝构造函数和赋值操作符为私有。

为什么这么做?

我们都知道Qt对C++增加了一下功能,signals、slots、object properties,events、event filters、string translation、timers、object trees、guarded pointers;dynamic cast。

新加入的这些功能要求我们把每一个QObject的对象看做是唯一(identities)的。唯一的意思就是不能通过拷贝或赋值操作制作出一个一模一样的复制体来。试想一下,如果我们哟一个QPushbutton对象btnSubmit,如果我们可以复制出一个和btnSubmit完全一样的Button对象,那么新的button对象的名字应该是什么?如果也叫btnSubmit,当我们给其中的btnSubmit接受时间或者发出信号时,系统如何区分事件由那个button对象接收或者那个对象发送?

我们知道在各种容器中能以value方式存放的类型,必须有默认的构造函数,拷贝构造函数和赋值操作。由于QObject及所有继承自它的子类都没有提供拷贝构造函数和赋值操作,当我们使用QList<QObject>时,编译器就会报错。如果我们要在容器中存储这样类型的对象,我们就要使用他们的指针,如QList<QObject *>

3 为什么QObject子类不可赋值

搬运链接:https://zhuanlan.zhihu.com/p/92163989

如果您尝试赋值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有几个原因,其中两个最大的原因是:

  • QObject之间通信通常使用信号和槽机制进行同行,不清楚连接的信号和槽是否应该转移到副本,如果他们将被转移,则意味着其他QObject自动定义该副本,这很可能会给开发人员代买混乱和不必要的副作用。
  • QObject被组织在对象树中,通常一个QObject的一个示例有一个父对象和几个子对象,在这个层次结构中副本应该组织在案例,孩子也应该被复制吗?

其他原因,但可能不那么重要是:

  • 一个QObject可以被认为是唯一,方法是给他一个可以用作参考键的名称,即通过设置QObject::objectName()。如果设置名称,则不清楚副本该指定那个名称了
  • QObject可以在运行时使用新的属性进行扩展,副本是否也应该继承这些新的属性?

一般来说,QObject是通过他们的指针被其他对象引用的。例如前面提到信号槽机制就是这种情况,因此,QObject如果移动了,他们之间的关联关系就会消失。在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_DISBALE_COPY在子类中重复

在Stack Overflow帖子建议总是在你自己的类中重复声明宏Q_DISBALE_COPY,即:

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的人可以快速理解其用法:不应(也不能)复制对象!

参考链接

http://www.manongjc.com/detail/14-fimypxrkamlcrex.html

https://www.cnblogs.com/senior-engineer/p/6196935.html

https://blog.csdn.net/a3121772305/article/details/85301851

https://zhuanlan.zhihu.com/p/92163989

https://www.iteye.com/blog/wan-2004-1134186

https://www.bbsmax.com/A/E35p6OOKzv/


博客签名2021
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DreamLife.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值