【问题标题】:Would a derived class ever have an implicit copy constructor or assignment operator when it's deleted in the base class?派生类在基类中删除时是否会有隐式复制构造函数或赋值运算符?
【发布时间】:2021-09-05 17:16:22
【问题描述】:

Qtdefines Q_DISABLE_COPY如下:

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

Q_DISABLE_COPYused in the QObject class,但 the documentation for it 表示它也应该在其所有子类中使用:

当您创建自己的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 的文档在这方面是错误的,还是在创建它们时存在一些极端情况?

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

【问题讨论】:

    标签: c++ copy-constructor derived-class copy-assignment deleted-functions


    【解决方案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_ 正确指出的那样,在Derived 的上述实现中,Base 没有被复制构造。我只是想说明一个事实,即在继承层次结构中修改另一个类时很容易引入无意的复制构造函数。

    【讨论】:

    • 这不是我真正要问的。我在问如果您不自己编写它们,编译器是否会为您生成它们。
    • 这不应该发生。如果派生类不知道如何复制其基类,则为该派生类自动生成一个复制构造函数也意味着为基类自动生成一个复制构造函数(您明确要求编译器不要这样做)。
    • @JosephSible-ReinstateMonica Qt 至少在过去支持 Q_DISABLE_COPY 的 C++11 前版本,它只是将构造函数声明为私有,没有“删除”。该语句可能是从旧文档继承的。就像下面的语句指出如果存在该宏,则不能执行 QWidget w = QWidget();(您可以使用现代 C++,但这就是 Qt 代码如此喜欢使用原始指针的原因之一和新的表达方式)
    • 这不是极端情况,因为您不复制 Base。可以将移动构造函数添加到 Base 并在 Derived 移动构造期间移动 Base,但它也不是复制。
    • 所以有 Q_DISABLE_MOVE 来覆盖它
    【解决方案2】:

    在提交 a2b38f6 之前,QT_DISABLE_COPY 被定义为 like this(感谢 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_DELETElike this

    #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 的维护人员删除/改写他们文档的该部分。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-07-27
      • 2014-05-16
      • 2020-04-13
      • 2013-06-22
      • 1970-01-01
      • 2020-04-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多