5

Qt定义Q_DISABLE_COPY如下:

#define Q_DISABLE_COPY(Class) \
    Class(const Class &) = delete;\
    Class &operator=(const Class &) = delete;

Q_DISABLE_COPYQObjectclass中使用,但它的文档说它也应该在其所有子类中使用:

当您创建自己的QObject子类(直接或间接)时,不应它一个复制构造函数或赋值运算符。然而,仅仅从你的类中省略它们可能是不够的,因为如果你错误地编写了一些需要复制构造函数或赋值运算符的代码(这很容易做到),你的编译器会精心为你创建它。你必须做得更多。

但是考虑这个程序:

struct Base {
    Base() = default;

private:
    Base(const Base &) = delete;
    Base &operator=(const Base &) = delete;
};

struct Derived : Base {};

int main() {
    Derived d1;
    Derived d2(d1); // error: call to implicitly-deleted copy constructor of 'Derived'
    Derived d3;
    d3 = d1; // error: object of type 'Derived' cannot be assigned because its copy assignment operator is implicitly deleted
}

尝试编译该程序的错误似乎表明,当在基类中删除派生类时,编译器不会在派生类中创建复制构造函数或赋值运算符。Qt 的文档在这方面是错误的,还是在创建它们时存在一些极端情况?

相关但不重复:在 QObject 派生类中重复 Q_DISABLE_COPY。它给出了在类中使用它可能有用的原因,Q_DISABLE_COPY即使它无论如何都不可复制,但并不能确认没有它它实际上永远不可复制。

4

2 回答 2

0

由于基类复制构造函数被删除,派生类无法知道如何复制基类对象。这将禁用编译器提供的任何隐式复制构造函数。

来自 cppreference:

如果以下任一条件为真,则类 T 的隐式声明或默认复制构造函数被定义为已删除:

  • T 具有无法复制的直接或虚拟基类(已删除、不可访问或不明确的复制构造函数)

  • T 具有带有已删除或不可访问的析构函数的直接或虚拟基类;

当用户从删除默认复制构造函数但提供其默认实现来覆盖它的类继承时,继承Q_DISABLE_COPY可能很有用。

struct Base {
    Base() = default;

private:
    Base(const Base &) = delete;
    Base &operator=(const Base &) = delete;
};

struct Derived : Base {
    Derived() = default;
    Derived(const Derived&) : Derived() {}
    Derived &operator=(const Derived&) {
        return *this;
    }
};

struct MoreDerived : Derived {};


int main() {
    Derived d1;
    Derived d2(d1); // Works fine!
    Derived d3;
    d3 = d1; // Works fine!
    MoreDerived md1;
    MoreDerived md2(md1); // Works fine!
    MoreDerived md3;
    md3 = md1; // Works fine!!
}

编辑:正如@SR_ 正确指出的那样,在上述实现中DerivedBase没有被复制构造。我只是想说明一个事实,即在继承层次结构中修改另一个类时很容易引入无意的复制构造函数。

于 2021-06-22T06:53:57.117 回答
0

在 commit 之前a2b38f6QT_DISABLE_COPY它的定义是这样的(感谢Swift - Friday Pie在评论中指出这一点):

#define Q_DISABLE_COPY(Class) \
    Class(const Class &) Q_DECL_EQ_DELETE;\
    Class &operator=(const Class &) Q_DECL_EQ_DELETE;

Q_DECL_EQ_DELETE 这样:

#ifdef Q_COMPILER_DELETE_MEMBERS
# define Q_DECL_EQ_DELETE = delete
#else
# define Q_DECL_EQ_DELETE
#endif

Q_COMPILER_DELETE_MEMBERS如果 C++11 支持(或至少有足够新的支持草案= delete)可用,则得到定义。

因此,如果您当时针对 C++03 编译器编译 Qt,它会编译如下内容:

struct Base {
    Base() {};

private:
    Base(const Base &);
    Base &operator=(const Base &);
};

struct Derived : Base {};

int main() {
    Derived d1;
    Derived d2(d1);
    Derived d3;
    d3 = d1;
}

编译它g++ -std=c++03会给你这些错误:

<source>: In copy constructor 'Derived::Derived(const Derived&)':
<source>:9:8: error: 'Base::Base(const Base&)' is private within this context
    9 | struct Derived : Base {};
      |        ^~~~~~~
<source>:5:5: note: declared private here
    5 |     Base(const Base &);
      |     ^~~~
<source>: In function 'int main()':
<source>:13:18: note: synthesized method 'Derived::Derived(const Derived&)' first required here
   13 |     Derived d2(d1);
      |                  ^
<source>: In member function 'Derived& Derived::operator=(const Derived&)':
<source>:9:8: error: 'Base& Base::operator=(const Base&)' is private within this context
    9 | struct Derived : Base {};
      |        ^~~~~~~
<source>:6:11: note: declared private here
    6 |     Base &operator=(const Base &);
      |           ^~~~~~~~
<source>: In function 'int main()':
<source>:15:10: note: synthesized method 'Derived& Derived::operator=(const Derived&)' first required here
   15 |     d3 = d1;
      |          ^~

所以当时,“你的编译器会为你精心创建它”在技术上是正确的,但实际上并非如此,因为创建它的编译器会导致编译失败,只是出现不同的(并且可以说不太清楚)错误。我现在确信现在= delete无条件使用它不再是真的了,所以我计划要求 Qt 的维护者删除/改写他们文档中的那一部分。

于 2021-06-22T16:18:31.310 回答