c++copy-constructorderived-classcopy-assignmentdeleted-functions

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


Qt defines Q_DISABLE_COPY as follows:

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

Q_DISABLE_COPY is used in the QObject class, but the documentation for it says that it should be used in all of its subclasses as well:

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.

But consider this program:

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
}

The errors from trying to compile that program seem to indicate that the compiler will not create copy constructors or assignment operators in derived classes when they're deleted in base classes. Is Qt's documentation just wrong about this, or is there some edge case when it would create them?

Related, but not a duplicate: Repeating Q_DISABLE_COPY in QObject derived classes. It gives reasons why it may be useful to use Q_DISABLE_COPY in a class even if it wouldn't be copyable anyway, but doesn't confirm that it never will in fact be copyable without it.


Solution

  • Prior to commit a2b38f6, QT_DISABLE_COPY was instead defined like this (credit to Swift - Friday Pie for pointing this out in a comment):

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

    And Q_DECL_EQ_DELETE like this:

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

    Q_COMPILER_DELETE_MEMBERS got defined if C++11 support (or at least a new enough draft of it to support = delete) was available.

    Thus, if you compiled Qt back then against a C++03 compiler, it would instead have compiled something like this:

    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;
    }
    

    And compiling that with g++ -std=c++03 gives you these errors:

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

    So back then, "your compiler will thoughtfully create it for you" was technically true but not practically so, since the compiler creating it would cause compilation to fail, just with a different (and arguably less clear) error. I'm now convinced that it's not true at all anymore now that = delete is unconditionally used, so I plan to ask Qt's maintainers to remove/reword that section of their documentation.