76

It seems that a vector will check if the move constructor is labeled as noexcept before deciding on whether to move or copy elements when reallocating. Is the default move constructor defined as noexcept? I saw the following documentation but it didn't specify this.http://en.cppreference.com/w/cpp/language/move_constructor

Implicitly-declared move constructor

If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true: there are no user-declared copy constructors there are no user-declared copy assignment operators there are no user-declared move assignment operators there are no user-declared destructors the implicitly-declared move constructor is not defined as deleted due to conditions detailed in the next section then the compiler will declare a move constructor as an inline public member of its class with the signature T::T(T&&) A class can have multiple move constructors, e.g. both T::T(const T&&) and T::T(T&&). If some user-defined move constructors are present, the user may still force the generation of the implicitly declared move constructor with the keyword default.

sad1raf
  • 125
  • 8
bjackfly
  • 2,928
  • 1
  • 22
  • 31

1 Answers1

81

I think the answer is 15.4/14 (Exception specifications):

An inheriting constructor (12.9) and an implicitly declared special member function (Clause 12) have an exception-specification. If f is an inheriting constructor or an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f’s implicit definition; f allows all exceptions if any function it directly invokes allows all exceptions, and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions.

Basically, it Does What You Think, and the implicitly-declared move constructor is noexcept whenever it can be.

Kerrek SB
  • 428,875
  • 83
  • 813
  • 1,025
  • 53
    Additional information: And you can test whether or not your expectations have been met: `static_assert(std::is_nothrow_move_constructible::value, "MyType should be noexcept MoveConstructible");` – Howard Hinnant Sep 06 '13 at 15:17
  • 1
    So all functions invoked by the implicit special member functions must be declared `noexcept` for the implicit special member functions to be `noexcept`. Means you have to be diligent enough to mark all relevant functions `noexcept`, means there is a lot room for human error, right? – mucaho Nov 03 '15 at 13:29
  • 2
    @mucaho: Well, if all your members themselves only use implicitly defined special members, then this isn't all that complex. The simple rule is the rule of single responsibility, and by default the only human error you should watch out for is defining special member functions explicitly. That only leaves special-purpose classes (such as `unique_ptr`) that you need to vet. – Kerrek SB Nov 04 '15 at 00:11
  • 9
    What about explicitly declared special member function with `default`? Such as `void T(T &&) = default`. Am I correct to assume that it behaves exactly the same if this move constructor is implicitly declared if not prevented by other conditions, such as a user-defined copy constructor? – Yan Zhou Dec 14 '16 at 16:28
  • 3
    @YanZhou Yes, explicitly defaulted Special member functions also follow those rules unless explicitly overridden for `constexpr` and `noexcept`. Your example has a spurious `void` though, so it is a compile-error. – Deduplicator Aug 14 '18 at 16:56
  • @HowardHinnant Your helpful comment to `static_assert(std::is_nothrow_move_constructible::value)` is a little misleading because that check will pass even if `MyType` has a `nothrow/noexcept` copy constructor (in which case the compiler makes no default move constructor, thus probably not being what the programmer expects since then `std::vector` copies). This includes both the cases of explicitly written AND compiler-generated implicit copy constructor if they happen to be `noexcept`....(continued) – Louis Semprini Sep 24 '20 at 19:10
  • @HowardHinnant (continued)...so given that rub, I'm not sure if the check is useful in the cases where the programmer hasn't explicitly also written a copy constructor that they know is not `noexcept` (because it's too much brainwork to figure out if the compiler-generated move-constructor would exist (despite your incredibly useful video https://www.youtube.com/watch?v=vLinb2fgkHk) and also whether the compiler-generated copy constructor would be `noexcept`). It's too bad there is no direct check that ONLY looks at the move constructor. – Louis Semprini Sep 24 '20 at 19:13
  • `vector` doesn't literally "look at the move constructor". In all likelihood it calls `move_if_noexcept`, or some equivalent: https://github.com/llvm/llvm-project/blob/master/libcxx/include/memory#L1546 `move_if_noexcept` branches on `is_nothrow_move_constructible`: http://eel.is/c++draft/forward#7 – Howard Hinnant Sep 24 '20 at 20:59
  • @HowardHinnant Right, it doesn't (confirmed by https://vimeo.com/97337253 at 38:05), but I'm saying neither does `std::is_nothrow_move_constructible*`, so that's why the test might be misleading to the OP and others...even if that test is true, meaning that `vector` will "do the right thing" by calling `MyType(std::move(bar))` rather than `MyType(bar)`, it turns out the call still ends up getting routed to the contained element's copy constructor, not its move constructor (continued)... – Louis Semprini Sep 24 '20 at 21:08
  • @HowardHinnant I think most readers of this question will assume the test checks whether the move constructor is called, but that's not the case. It would be nice if there could be such a test in C++, but it seems like the ideal test is not possible. So it would be good to warn people of the limitations of the (still useful) `std::is_nothrow_move_constructible*` test. – Louis Semprini Sep 24 '20 at 21:09
  • The test answers whether or not the _expression_ `MyType(std::move(bar))` is a valid, `noexcept` expression. This is the expression that `vector` will use. Asking if a move member exists or not is a different issue, and not nearly as helpful in generic programming. – Howard Hinnant Sep 24 '20 at 21:12
  • @HowardHinnant Right, but the test people are hoping for isn't really "which path does `std::vector` take?" but rather "when all is said and done, will my MyType objects get copied or moved?" It's very easy for us to be fooled into a false confidence that our data is being moved especially in two cases: case #1: we wrote a copy constructor and didn't look at your cool video and understand that the compiler would not provide an implicit move constructor for us, and (continued) – Louis Semprini Sep 24 '20 at 21:21
  • @HowardHinnant case #2 we provided a destructor, causing the compiler to provide an implicit copy constructor (that could be noexcept) and omit the move constructor. In both cases we could easily assume that the compiler is providing a move constructor, and the `std::is_nothrow_move_constructible*==true` result could reinforce our false idea. I'll agree that this is a separate issue of "if you write one special function you should write all," however that second issue is likely to trip us up that I think it's worth mentioning whenever mentioning the `std::is_nothrow_move_constructible*` test. – Louis Semprini Sep 24 '20 at 21:22
  • Type authors need to give loving care to all 6 special members, whether or not they let the compiler provide them, and whether or not that type is to be used as an element in vector: http://howardhinnant.github.io/classdecl.html – Howard Hinnant Sep 24 '20 at 21:46
  • @HowardHinnant Haha yes agreed! Glad that we now have a link to that guide on this question. – Louis Semprini Sep 24 '20 at 21:55