C++11 virtual destructors and auto generation of move special functions
Asked Answered
C

1

29

The rules for auto generating special move functions (constructor and assignment operator) in C++11 specify that no destructor can be declared. The logic is presumably that, if you need to do something special in destruction, that a move may not be safe.

However, for proper destructor calls in polymorphism, it is necessary to declare a base classes' destructor as virtual (otherwise deleting an instance of a sub class through a pointer of its base class will not properly chain the destructor).

I'm assuming, then, that even an empty destructor would prevent the compiler from automatically generating a special move functions. As in:

class Base {
    virtual ~Base() { }
};

You can, however, default the destructor, as in:

class Base {
    virtual ~Base() = default;
}

So question 1: Will this allow the compiler to auto generate special move functions?

There is a problem with the explicit default destructor, however. In at least the case of GCC 4.8.2, the signature is implicitly changed to noexcept. As in:

class Base {
    virtual ~Base() = default; // compiler changes to:
    // virtual ~Base() noexcept;
}

While I have no problem with noexcept in a destructor, this would break the following "client" code:

class Sub : public Base {
    virtual ~Sub(); // this declaration is now "looser" because of no noexcept
}

So question 2 is more to the point: is there a way to allow auto generation of special move functions in C++11 and allow proper destructor chaining to sub classes (as described above), all without breaking subclass ("client") code?

Credenza answered 26/3, 2015 at 22:20 Comment(5)
Why is ~Sub looser? Destructors with no exception specifications default to noexcept.Brokenhearted
The question seems well defined, but out of interest, do you have an example of why this might be needed? This seems like a strange mixture of value and reference semantics. It seems to me like in cases where you want to use moving (or even copying) you wouldn't want polymorphic behaviour. And more to the point the default copy or move definitely won't have any polymorphic behaviour.Speckle
@Speckle the goal is to rework base classes in such a way that they and their subclasses can take advantage of move semantics without breaking sub classes.Credenza
@Brokenhearted When I tried that, GCC produced errors about the looser definition.Credenza
@Brokenhearted btw, that's not correct, see here: https://mcmap.net/q/438121/-default-destructor-nothrowCredenza
A
28
  1. No, a defaulted destructor is still considered user defined, so it will prevent the generation of move operations. Also declare the move operations default-ed to make the compiler generate them.

  2. You need to only declare the move operations as default-ed in the base class. In the derived class, the destructor won't be user defined anymore (unless you explicitly say so), so the move operations won't be deleted.

So what I'd do is the following:

class Base
{
    virtual ~Base() = default;
    Base(Base&&) = default;
    Base& operator=(Base&&) = default;
    // probably need to think about copy operations also, as the move disables them
    Base(const Base&) = default;
    Base& operator=(const Base&) = default;
};

I highly recommend this talk by the person who contributed probably the most to the move semantics: http://www.slideshare.net/ripplelabs/howard-hinnant-accu2014

Or, if you can get your hands on, you should read the Item 17: Understand special member function generation from Scott Meyers' excellent book Effective Modern C++. This issue is excellently explained.

PS: I think you should think a bit more about your base classes. Most of the time, you should use abstract classes, so there will be no need to copy/move instances of them.

PSS: I think by default destructors are marked noexcept in C++11/14, so not explicitly specifying it shouldn't cause any problems:

Inheriting constructors and the implicitly-declared default constructors, copy constructors, move constructors, destructors, copy-assignment operators, move-assignment operators are all noexcept(true) by default, unless they are required to call a function that is noexcept(false), in which case these functions are noexcept(false).

Attendance answered 26/3, 2015 at 22:22 Comment(3)
Thanks, I think this solves the problem (and answers the questions). I was actually reading Item 17 in Scott's book when I came up with this question :)Credenza
@stephelton glad it helped. About the "looser nothrow" specification, I think I bumped into this before (g++4.8.x), it is a compiler issue, will try to find the link and post it hereAttendance
@stephelton found it, see here: https://mcmap.net/q/438121/-default-destructor-nothrowAttendance

© 2022 - 2024 — McMap. All rights reserved.