Why do we need to use virtual ~A() = default; instead of virtual ~A() {} in C++11?
Asked Answered
N

3

49

In Stack Overflow post Checking the object type in C++11, I have the comment:

In C++11 you'll actually want to do virtual ~A() = default; Otherwise, you'll lose the implict move constructors.

What is virtual ~A() = default; for? How come implicit move constructors lost with virtual ~A() {}?

Nadiya answered 20/6, 2013 at 19:0 Comment(2)
+1 because you managed to cause wrong answers from 10k+ users.Attire
Note that you can always get the move constructor anyways with the same mechanic: A(A&&) = default;Boliviano
R
42

The comment is not correct.

Both:

virtual ~A() = default;

and

virtual ~A() {}

are user declared. And the implicit move members are inhibited if the destructor is user declared.

[dcl.fct.def.default]/p4 discusses user-declared and user-provided special members:

A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.

Rostand answered 20/6, 2013 at 19:26 Comment(5)
@HowardHinnant: Then in both cases the compiler wouldn't provide a move constructor?Mollescent
@Pixelchemist: I think you're right. Section 8.4.2 Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them. A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. This is from n3337 i.e. C++11 standard + 1Mollescent
I thought that the entire point of defaulted functions was to NOT suppress generation of other members, impact triviality, etc.Dashpot
@DeadMG: No, the point of defaulting is to get members without a ton of boilerplate when they were suppressed for whatever reason.Boliviano
@Boliviano There are other reasons to have non-user-provided functions as well. E.g. ~A() = default; may be a trivial destructor but ~A() {} cannot be.Cohbath
A
27

In this post https://mcmap.net/q/356669/-checking-the-object-type-in-c-11, I have the comment:

In C++11 you'll actually want to do virtual ~A() = default; Otherwise, you'll lose the implict move constructors.

The comment is incorrect.

Even defaulted, that destructor is "user-declared" (though note that it is not also "user-provided").

#include <iostream>

struct Helper
{
    Helper() {}
    Helper(const Helper& src) { std::cout << "copy\n"; }
    Helper(Helper&& src)      { std::cout << "move\n"; }
};

struct A
{
    virtual ~A() {}
    Helper h;
};

struct B
{
    virtual ~B() = default;
    Helper h;
};

struct C
{
    Helper h;
};


int main()
{
    {
        A x;
        A y(std::move(x));   // outputs "copy", because no move possible
    }

    {
        B x;
        B y(std::move(x));   // outputs "copy", because still no move possible
    }

    {
        C x;
        C y(std::move(x));   // outputs "move", because no user-declared dtor
    } 
}

Live demo:

+ g++-4.8 -std=c++11 -O2 -Wall -pthread main.cpp
+ ./a.out
copy
copy
move

So you haven't "lost" anything — there was no move functionality there to begin with!

Here is the standard passage that prohibits an implicit move constructor in both cases:

[C++11: 12.8/9]: If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor,
  • X does not have a user-declared copy assignment operator,
  • X does not have a user-declared move assignment operator,
  • X does not have a user-declared destructor, and
  • the move constructor would not be implicitly defined as deleted.

Bootnote

It wouldn't hurt if a future version of the standard actually listed the precise meanings of terms such as "user-declared". There is, at least, this:

[C++11: 8.4.2/4]: [..] A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. [..]

One may assume the distinction here by implication.

Attire answered 20/6, 2013 at 19:33 Comment(4)
Although I wonder why the standard doesn't allow the implementation to provide a move constructor when none is provided by the user i.e. leaving it undeclared and doing = default; seems the same.Mollescent
@legends2k: I agree; ideally 12.8/9 could say "user-provided" instead. Probably 12.8/20, too?Attire
These implicit special member function rules have gotten more complicated in C++11. What does it mean "explicitly defaulted or deleted on its first declaration"? Can there be a subsequent declaration after the first?Tripos
@ThomasMcLeod: A definition is also a declaration so, yes... except you can't define a defaulted or deleted special member function. There would be two declarations here: struct A { ~A() = default; }; A::~A() {} except that the definition is illegal. I think it's probably just awkward or over-precise wording.Attire
M
5

That comment is wrong.

Instead of providing your own move constructor, if you want the compiler to provide one, one of the requirements is that it expects that the destructor is also provided by it i.e. a trivial destructor. However, the current standard is pretty strict on when an implicit implementation can be provided — in accepting how a destructor is given by the user. Anything declared by the user is considered that the user is taking the matter into their own hands and thus not only this

~A() { … }

but also this

~A() = default;

makes the compiler not provide an implicit destructor. First is a definition and thus a declaration too; second is just a declaration. In both cases the destructor is user-declared and thus prohibits the compiler from providing an implicit move constructor.

I guess the rationale behind the requirement is that during move an object's resources are moved to another object leaving the original object in a state where it has no resources in dynamic-storage; but if your class doesn't have any such resources then it can be trivially moved, destroyed, etc. When you declare a non-trivial destructor it's a cue for the compiler that the resources you manage in the class are not something trivial and that you'd mostly have to provide non-trivial move too, so the compiler doesn't provide one.

Mollescent answered 20/6, 2013 at 19:5 Comment(2)
Except that it is not the reasoning behind this behaviour, because that is not the behaviour.Attire
Fixed now to address OPs question.Mollescent

© 2022 - 2024 — McMap. All rights reserved.