4

Given the following code (http://liveworkspace.org/code/5oact):

class Foo
 {
     public:
         Foo()
         {
             log(__PRETTY_FUNCTION__);
         }
         Foo(const Foo& other)
         {
             log(__PRETTY_FUNCTION__);
         }
         Foo& operator=(const Foo& other)
         {
             log(__PRETTY_FUNCTION__);
             return *this;
         }
         Foo(Foo&& other) noexcept
         {
             log(__PRETTY_FUNCTION__);
         }
         Foo& operator=(Foo&& other) noexcept
         {
             log(__PRETTY_FUNCTION__);
             return *this;
         }
         ~Foo(){}
 };

Using the class like this:

std::vector<Foo> tt;

tt.emplace_back();
tt.emplace_back();
tt.emplace_back();
tt.emplace_back();

I get the following output:

Foo::Foo()
Foo::Foo()
Foo::Foo(const Foo&)
Foo::Foo()
Foo::Foo(const Foo&)
Foo::Foo(const Foo&)
Foo::Foo()

and if I remove the custom destructor I get the following output:

Foo::Foo()
Foo::Foo()
Foo::Foo(Foo&&)
Foo::Foo()
Foo::Foo(Foo&&)
Foo::Foo(Foo&&)
Foo::Foo()

Why the compiler uses the copy constructor instead of the move when I declare a destructor? I understand that the move operation can't throw (and if I remove the noexcept from the code, the compiler won't use it at all), but what the destructor has to do with that?

Luke B.
  • 1,258
  • 1
  • 17
  • 28
  • 2
    A non-`noexcept` destructor has the same problem as a non-`noexcept` move constructor - you may throw when half the state is moved over, and you're screwed. See [this](http://liveworkspace.org/code/23ZbeJ). – Xeo Feb 17 '13 at 19:04
  • 1
    @Xeo: Aren't destructors `noexcept` by default? – K-ballo Feb 17 '13 at 19:05
  • @K-ballo: If you explicitly define them not to be (as the code in the question does), no, why would they? – Xeo Feb 17 '13 at 19:08
  • @K-ballo: yes, that's why the implicit one uses the move constructor. His explicit destructor isn't `noexcept`, so it uses the copy constructor instead. – Mooing Duck Feb 17 '13 at 19:08
  • 1
    @Xeo: I was under the impression that an explicit constructor with no explicit `noexcept` specification would be declared as if it had the `noexcept(true)` specification. Did that proposal never made it? – K-ballo Feb 17 '13 at 19:12
  • @K-ballo: Yeah nvm, seems you're right. GCC 4.7 just doesn't do that, 4.8 and Clang 3.2 do. Still, the reasoning in my comment holds, since it's `noexcept(false)` for GCC 4.7. :) – Xeo Feb 17 '13 at 19:14
  • 2
    @Xeo: The important standard bit is `12.4.3`, which says that a destructor with no explicit exception specification has the some exception specification that an implicitly declared destructor. – K-ballo Feb 17 '13 at 19:17
  • 2
    @MooingDuck: His explicit destructor happens to be `noexcept`, this seems to be a compiler issue. – K-ballo Feb 17 '13 at 19:18
  • @Xeo Adding noexcept to the destructor did work, but I guess I'll just upgrade to gcc 4.8. Could you write an answer? – Luke B. Feb 17 '13 at 19:21
  • GCC 4.8 is still in development, I was using a snapshot. Also, K-ballo's answer nails it. – Xeo Feb 17 '13 at 19:30

1 Answers1

6

First, it seems that there is an issue with your compiler that uses the wrong noexcept specification. According to the standard, 12.4.3:

A declaration of a destructor that does not have an exception-specification is implicitly considered to have the same exception-specification as an implicit declaration

An implicit declaration of a destructor would be noexcept if all the members and bases' destructors are noexcept as well. So your explicit destructor declaration should be equivalent to:

~Foo() noexcept {} // or:
~Foo() noexcept(true) {}

but instead your compiler is treating it as:

~Foo() noexcept(false) {}

Second, the reason the exception-specification of a destructor affects the decision on whether to move or not is because destruction is involved in the operation. Just as noexcept on a move-constructor and a move-assignment operation affect the decision, move won't be used if there is a possibility that an exception might be thrown mid-process.

K-ballo
  • 80,396
  • 20
  • 159
  • 169