问题
In his GoingNative 2013 talk, Scott Meyers pointed out that std::move
is no guarantee that the generated code will actually perform a move.
Example:
void foo(std::string x, const std::string y) {
std::string x2 = std::move(x); // OK, will be moved
std::string y2 = std::move(y); // compiles, but will be copied
}
Here, the move constructor cannot be applied but because of overload resolution, the normal copy constructor will be used instead. This fallback option may be crucial for backward compatibility with C++98 code, but in the example above it is most likely not what the programmer intended.
Is there a way to enforce that a move constructor will be called?
For example, assume that you want to move a huge matrix. If your application really depend on the Matrix to be moved, it would be great to immediately get a compile error if a move is not possible. (Otherwise, you the performance problem may slip easily through unit tests and you will only find out after some profiling.)
Lets call this guaranteed move strict_move
. I would like to be able to write code like this:
void bar(Matrix x, const Matrix y) {
Matrix x2 = strict_move(x); // OK
Matrix y2 = strict_move(y); // compile error
}
Is it possible?
Edit:
Thanks for the great answers! There were some legitimate requests to clarify my question:
- Should
strict_move
fail if the input is const? - Should
strict_move
fail if the result will not lead to an actual move operation (even though the copy might be as fast as a move, e.g.,const complex<double>
)? - Both?
My original idea was very vague: I considered Scott Meyers examples quite alarming, so I wondered if it is possible to have the compiler prevent such unintended copies.
Scott Meyers mentioned in his talk that a general compiler warning is not an option as it would result in a huge number a false positives. Instead I want to communicate to the compiler something like "I'm 100% sure that this must always resulting in a move operation and a copy is too expensive for this specific type".
Thus, I would have offhandedly said that strict_move
should fail in both cases. Meanwhile I'm not sure what would be best. Another aspects that I didn't consider is noexcept
.
From my side, the exact semantics of strict_move
are open. Everything that helps to prevent some dumb mistakes at compile time without having serious drawbacks is fine.
回答1:
I advise against writing a general strict_move
that is detecting const
. I think that is not really what you're looking for. Do you want this to flag a const complex<double>
, or a const pair<int, int>
? These types will copy as fast they move. Flagging them would just be an irritant.
If you want to do this, I recommend instead checking to see if the type is noexcept MoveConstructible
. This will work perfectly for std::string
. If the copy constructor of string
is accidentally called, it is not noexcept, and therefore will be flagged. But if the copy constructor of pair<int, int>
is accidentally called, do you really care?
Here is a sketch of what this would look like:
#include <utility>
#include <type_traits>
template <class T>
typename std::remove_reference<T>::type&&
noexcept_move(T&& t)
{
typedef typename std::remove_reference<T>::type Tr;
static_assert(std::is_nothrow_move_constructible<Tr>::value,
"noexcept_move requires T to be noexcept move constructible");
static_assert(std::is_nothrow_move_assignable<Tr>::value,
"noexcept_move requires T to be noexcept move assignable");
return std::move(t);
}
I decided to check against is_nothrow_move_assignable
as well, as you don't know whether the client is constructing or assigning the lhs.
I opted for internal static_assert
instead of an external enable_if
because I don't expect noexcept_move
to be overloaded, and the static_assert
will yield a clearer error message when triggered.
回答2:
First I would like to state that you previous answers will not solve your problem completely.
Counterexample where previous solutions (@Kerrerk and @0x499602D2) fail: assume that you wrote your matrix class with a move constructor that throws an exception. Now assume that you want to move std::vector<matrix>
. This article shows that you can't have the "strong exception guarantee" if the matrix class elements that the std::vector<matrix>
holds were actually moved (what happen if the jth element move constructor throws an exception ? You would lose data because there is no way to recover the elements you have already moved!).
That is why stl containers implement .push_back()
, .reserve()
and their move constructor using std::move_if_noexcept
to move the elements they hold. Here is an example implementation of reserve() taken from open-std :
void reserve(size_type n)
{
if (n > this->capacity())
{
pointer new_begin = this->allocate( n );
size_type s = this->size(), i = 0;
try
{
for (;i < s; ++i)
new ((void*)(new_begin + i)) value_type( std::move_if_noexcept( (*this)[i]) ) );
}
catch(...)
{
while (i > 0) // clean up new elements
(new_begin + --i)->~value_type();
this->deallocate( new_begin ); // release storage
throw;
}
// -------- irreversible mutation starts here -----------
this->deallocate( this->begin_ );
this->begin_ = new_begin;
this->end_ = new_begin + s;
this->cap_ = new_begin + n;
}
}
So, if you don't enforce that your move and default constructor is noexcept, you won't guarantee that functions like std::vector.resize() or std::move (for stl containers) will never copy the matrix class and that is the correct behavior (otherwise you may lose data)
回答3:
You can just make your own version of move
that doesn't allow constant return types. For example:
#include <utility>
#include <string>
#include <type_traits>
template <typename T>
struct enforce_nonconst
{
static_assert(!std::is_const<T>::value, "Trying an impossible move");
typedef typename std::enable_if<!std::is_const<T>::value, T>::type type;
};
template <typename T>
constexpr typename enforce_nonconst<typename std::remove_reference<T>::type>::type &&
mymove(T && t) noexcept
{
return static_cast<typename std::remove_reference<T>::type &&>(t);
}
void foo(std::string a, std::string const b)
{
std::string x = std::move(a);
std::string y = std::move(b);
}
void bar(std::string a, std::string const b)
{
std::string x = mymove(a);
// std::string y = mymove(b); // Error
}
int main() { }
来源:https://stackoverflow.com/questions/18646874/force-a-compile-time-error-if-stdmove-will-result-in-an-unintended-copy