5

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.

2 Answers2

0

Since the base class copy constructor is deleted, the derived class has no way to know how to copy the base class object. This will disable any implicit copy constructors provided by the compiler.

From cppreference:

The implicitly-declared or defaulted copy constructor for class T is defined as deleted if any of the following conditions are true:

  • T has direct or virtual base class that cannot be copied (has deleted, inaccessible, or ambiguous copy constructors)

  • T has direct or virtual base class with a deleted or inaccessible destructor;

Inheriting Q_DISABLE_COPY can be useful when the user inherits from a class that deletes the default copy constructor, but provides their default implementation to override it.

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

Edit: As @SR_ rightly states, in the above implementation of Derived, Base is not being copy constructed. I just wanted to illustrate the fact that it is easy to introduce an unintentional copy constructor when another class is modified in the inheritance hierarchy.

Rajat Jain
  • 452
  • 1
  • 6
  • 16
  • This isn't really what I'm asking about. I'm asking whether the compiler will generate them for you if you don't write them yourself. – Joseph Sible-Reinstate Monica Jun 22 '21 at 06:56
  • That shouldn't happen. If the derived class doesn't know how to copy its base, auto-generating a copy constructor for that derived class would mean auto-generating a copy constructor for the base class as well (which you explicitly ask the compiler to not do). – Rajat Jain Jun 22 '21 at 06:59
  • 2
    @JosephSible-ReinstateMonica Qt at least in past supported pre C++11 version of Q_DISABLE_COPY which was just declaring constructors private, there was no "delete". That statement might be inherited from older document., just like following one which states that you cannot do `QWidget w = QWidget();` if that macro present (you can with modern C++, but that was one of reasons why Qt code is so fond of raw pointer use and new expression) – Swift - Friday Pie Jun 22 '21 at 07:09
  • This is no edge case because you do not copy Base. One can add a move constructor to Base and move Base during a Derived move construction, but it's not copy either. – SR_ Jun 22 '21 at 07:10
  • So there is Q_DISABLE_MOVE to cover that – Swift - Friday Pie Jun 22 '21 at 07:11
  • @SR_ clarified the answer. – Rajat Jain Jun 22 '21 at 07:24
0

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.