1.前言
在编程中,会用到某些资源,这些资源有的在整个应用程序期间是唯一的;是不能通过拷贝、赋值的方法存在多份的,如STL的std::unique_ptr指针指向的资源。现实中这样的资源有:文件指针、串口句柄等。试想如果存在多个同一个文件的句柄或同一个串口的句柄,就可能在同一时刻对同一文件或串口进行写操作,这样会导致文件内容杂乱、损坏;现实中,当一个串口被一个程序占用时,另外一个程序尝试打开这个串口会报错。为了防止资源通过拷贝、赋值方式存在多份,Qt引入了Q_DISABLE_COPY、Q_DISABLE_MOVE、Q_DISABLE_COPY_MOVE宏。
2.删除函数、拷贝/移动构造函数、拷贝/移动赋值函数
说明:本节都是C++11中的知识,如果读者对本节很熟悉,可以跳过本节的学习。本节只是粗略提到这几个函数的说明,更深入的用法,可以自行学习C++11来进一步深入。
如下代码:
class MyClass{private:MyClass(const MyClass &) = delete; // 拷贝构造函数被删除MyClass(const MyClass &&) = delete;// 移动构造函数被删除MyClass &operator=(const MyClass &) = delete; // 拷贝赋值函数被删除MyClass& operator=(MyClass&&);// 移动赋值函数被删除,注意没有const};
在上面代码中,声明一个名为 MyClass的类。其中第4、5、6、7行分别为:复制构造函数、移动构造函数、拷贝赋值函数、移动赋值函数。因为这几个函数被标注为delete,即这几个函数被标注为删除的,所以调用这几个函数会报错。报错类似如下:
尝试引用已删除的函数
C++11正是通过这种方式保证资源无法通过拷贝、赋值方式存在多份。
说明:C++基础不扎实的读者就会发出疑问:既然外层不能调用这些函数,不在cpp文件中实现构造、赋值函数不就行了吗?为何还要费心费力的写那么多标记为delete的函数呢?按照C++的规则:这几个函数如果不手动写,则编译器会为我们默认生成,默认生成的函数按位进行拷贝,反映到拷贝构造函数上,就是浅拷贝,所以不写和手动写但标记为delete是完全不同的。
3.Qt这几个宏的说明
Q_DISABLE_COPY宏等同于2节第4、6行代码功能,即拷贝构造和拷贝赋值函数都被删除。
Q_DISABLE_MOVE宏等同于2节第5、7行代码功能,即移动构造和移动赋值函数都被删除。
Q_DISABLE_COPY_MOVE宏等同于2节第4、5、6、7行代码功能,即拷贝构造、拷贝赋值函数、移动构造、移动赋值函数都被删除。
使用这几个宏时需要注意如下几点:
本类表示的对象不能通过拷贝、赋值的方法存在多份,只能唯一存在。
当使用这几个宏时,将其放到类的private标识区。
这几个宏的参数为类名。
4.单例模式
对于在整个应用程序中,只能存在一份的对象,在设计中,一般通常采用单例设计模式。如下为Qt
下的单例模式的.h文件
class MyClass:public QObject{private:MyClass(QObject *parent = Q_NULLPTR);~MyClass();MyClass(const MyClass &) = delete; // 拷贝构造函数被删除MyClass(const MyClass &&) = delete;// 移动构造函数被删除MyClass &operator=(const MyClass &) = delete; // 拷贝赋值函数被删除MyClass& operator=(MyClass&&);// 移动赋值函数被删除,注意没有const
private:static MyClass* createAndGetInstance(); // 创建并返回MyClass类对象的唯一实例,也即m_pInstance
private:// 唯一实例static MyClass* m_pInstance;};
上面写了一大堆删除函数,对程序员来说是个负担,有了上面的宏后,则代码可以简写为:
class MyClass:public QObject{private:MyClass(QObject *parent = Q_NULLPTR);~MyClass();
private:Q_DISABLE_COPY_MOVE(MyClass)
private:static MyClass* createAndGetInstance(); // 创建并返回MyClass类对象的唯一实例,也即m_pInstance
private:// 唯一实例static MyClass* m_pInstance;};
关于单例模式的实现,可参考如下链接:
《单例》。
《单例模式(Qt实现)》。
《Q_GLOBAL_STATIC用法及如何保证多线程下的单例模式安全性》。
5.附加说明
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,如果我们可以复制出一个和btnSubmint完全一样的button对象,那么新的button对象的名字应该是什么?如果也叫btnSubmit,当我们给其中的btnSubmit接收事件或发出信号时,系统如何区分把事件由哪个button对象接收,或者哪个对象发送了信号?
我们知道在各种容器中能以value方式存放的类型,必须有默认的构造函数,拷贝构造函数和赋值操作。由于QObject及所有继承自它的子类都没有提供拷贝构造和赋值操作,当我们使用QList时,编译器就会报错。如果我们要在容器中存储这中类型的对象,我们就要使用它们的指针。如QList<QObject *>。如下代码:
QWidget w1 = QWidget();
上面的=号操作符会调用拷贝赋值函数,根据上述的规则,会报错,报错如下:
转到QWidget类的声明处,可以看到如下:
即QWidget的拷贝赋值函数被删除了。