当派生类在基类中删除时,它是否会具有隐式复制构造函数或赋值运算符?

Would a derived class ever have an implicit copy constructor or assignment operator when it's deleted in the base class?

提问人:Joseph Sible-Reinstate Monica 提问时间:6/22/2021 更新时间:6/23/2021 访问量:770

问:

QtQ_DISABLE_COPY的定义如下:

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

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

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

但考虑一下这个程序:

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

C++ 构造函数 生类 复制赋值 deleted-functions

评论


答:

0赞 Rajat Jain 6/22/2021 #1

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

从 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

评论

0赞 Joseph Sible-Reinstate Monica 6/22/2021
这不是我真正要问的。我问的是,如果你不自己编写它们,编译器是否会为你生成它们。
0赞 Rajat Jain 6/22/2021
这不应该发生。如果派生类不知道如何复制其基类,则为该派生类自动生成复制构造函数将意味着也为基类自动生成复制构造函数(您明确要求编译器不要这样做)。
2赞 Swift - Friday Pie 6/22/2021
@JosephSible-ReinstateMonica Qt 至少在过去支持的 C++11 之前版本的 Q_DISABLE_COPY 只是声明构造函数私有,没有“删除”。该语句可能继承自旧文档,就像以下声明如果存在该宏就不能执行此操作一样(您可以使用现代C++,但这是Qt代码如此喜欢原始指针使用和新表达式的原因之一)QWidget w = QWidget();
0赞 SR_ 6/22/2021
这不是极端情况,因为您没有复制 Base。可以向 Base 添加移动构造函数,并在派生移动构造期间移动 Base,但它也不是复制。
0赞 Swift - Friday Pie 6/22/2021
因此,有Q_DISABLE_MOVE可以涵盖这一点
0赞 Joseph Sible-Reinstate Monica 6/23/2021 #2

在提交 a2b38f6 之前,是这样定义的(感谢 Swift - Friday Pie 在评论中指出了这一点):QT_DISABLE_COPY

#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;
      |          ^~

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