Many C++ programmers (myself included) would like to have destructive move semantics. Even with the current proposal we can (unsafely) say almost that:
class RAII_Handle { public: // Post-condition: h_ is valid RAII_Handle(const char* c) : h_(::open(c)) { if (h_ == nullptr) { throw false; } } RAII_Handle(RAII_Handle&& h) : h_(h.h_) { h.h_ = nullptr; } // exposition only, not self-assignment safe RAII_Handle& operator=(RAII_Handle&& h) { ::close(h_); h_ = h.h_; h.h_ = nullptr; } // if h_ is null, calling ::close( ) may crash, must protect against it ~RAII_Handle( ) { if (h_ != nullptr) { ::close(h_); } } // if h_ is null, calling ::execute( ) may crash void command(const char* c) { ::execute(h_, c); } private: RAII_Handle(const RAII_Handle&); RAII_Handle& operator=(const RAII_Handle&); Handle* h_; };
The constructor postcondition is violated after a move. I should confess I thought this was the way it should work before reading the move proposal completely (which states that an object should be usable even after a move).
This unsafe design may be fixed by adding open( ), close( ), and is_open( ) member functions. However, if we want to use this kind of destructing move, the destructor must always protect himself from dereferencing a null pointer.
While a fully destructive move semantic would avoid this null-checking, the best solution I have read (from what other programmers propose) would allow stack-RAII'ed variables to leak resources; this is a -big no-. I fully agree with the current move proposal since its flexible enough anyway and not unsafe (unless the programmer really wants it be). For heap or placement-new'ed variables, however, the story may be different.
In the example showed above a just-moved variable's destructor will have no side effects, in fact it will just check if it must clean itself or not. This is true even for move-safe types; many classes will have to be redesigned (std::string in mind, with at least one character as short-string optimization) so move semantics actually allow the desirable performance boost. The effects of their destructors inmediatly after a move could be ignored in most cases. This document proposes to type-trait this information in the same way other type traits work.
Containers that separate destruction from deallocation can use this information to avoid calls to the destructors if just deallocating will suffice. This concept was already explained by some (see references) and I was quiet surprised not to see this as part of the standard. This will give us fully-destructive-like performance without actually implementing it. Implementors of containers are free to use or not this information.
template <class T> struct has_trivial_assign;
template <class T> struct has_trivial_destructor;
template <class T> struct has_trivial_destructor_after_move;
template <class T> struct has_trivial_reallocation;
template <class T> struct has_nothrow_default_constructor;
template <class T> struct has_nothrow_copy_constructor;
template <class T> struct has_trivial_destructor; | T is a trivial type (3.9) or a reference type or a class type with a trivial destructor (12.4) or an array of such a class type. | T shall be a complete type, an array of unknown bound, or (possibly cv-qualified) void. |
template <class T> struct has_trivial_destructor_after_move; | has_trivial_destructor<T>::value is true or T is a class type whose destructor has no side-effects inmediatly after moving the object to be destroyed, or an array of such a class type. | T shall be a complete type, an array of unknown bound, or (possibly cv-qualified) void. |
template <class T> struct has_trivial_reallocation; | T is a trivial type (3.9) or a reference type or a class type that can be reallocated in memory by bitwise-copying the object without destroying the source, or an array of such a class type. | T shall be a complete type, an array of unknown bound, or (possibly cv-qualified) void. |
template <class T> struct has_nothrow_default_constructor; | has_trivial_default_constructor<T>::value is true or T is a class type with a default constructor that is known not to throw any exceptions or T is an array of such a class type. | T shall be a complete type, an array of unknown bound, or (possibly cv-qualified) void. |
Special thanks to Howard Hinnant for his feedback.