The executive summary seems to be, "it's safe as long as you use it the usual way".
How safe is this method of emulating move-semantics in C++03?
-
20-09-2022 - |
سؤال
Using this answer, I invented my own method of emulating move-semantics in C++03 based on swap
.
First, I detect move-semantics (i.e. availability of C++03):
#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) || \
defined(_MSC_VER) && _MSC_VER >= 1600
#define HAS_MOVE_SEMANTICS 1
#elif defined(__clang)
#if __has_feature(cxx_rvalue_references)
#define HAS_MOVE_SEMANTICS 1
#else
#define HAS_MOVE_SEMANTICS 0
#endif
#else
#define HAS_MOVE_SEMANTICS 0
#endif
Then I conditionally define a macro called move
:
#if !HAS_MOVE_SEMANTICS
#include <algorithm>
namespace _cpp11_detail
{
template<bool B, class T = void> struct enable_if;
template<class T> struct enable_if<false, T> { };
template<class T> struct enable_if<true, T> { typedef T type; };
template<class T>
inline char (&is_lvalue(
T &, typename std::allocator<T>::value_type const volatile &))[2];
inline char (&is_lvalue(...))[1];
template<bool LValue, class T>
inline typename enable_if<!LValue, T>::type move(T v)
{ T r; using std::swap; swap(r, v); return r; }
template<bool LValue, class T>
inline typename enable_if<LValue, T>::type move(T &v)
{ T r; using std::swap; swap(r, v); return r; }
template<bool LValue, class T>
inline typename enable_if<LValue, T>::type const &move(T const &v)
{ return v; }
}
using _cpp11_detail::move;
namespace std { using _cpp11_detail::move; }
// Define this conditionally, if C++11 is not supported
#define move(...) move< \
(sizeof((_cpp11_detail::is_lvalue)((__VA_ARGS__), (__VA_ARGS__))) != 1) \
>(__VA_ARGS__)
#endif
Then I use it like this:
#include <vector>
std::vector<int> test(std::vector<int> v) { return std::move(v); }
int main()
{
std::vector<int> q(5, 5);
int x = 5;
int y = std::move(x);
std::vector<int> qq = test(std::move(test(std::move(q))));
}
My question is, how safe is this approach in practice? (assuming it compiles fine)
Are there any practical scenarios in which it could fail to work correctly in C++03 but not in C++11?
What about the opposite -- can it work correctly in C++11 but fail in C++03?
(Note: I'm looking for a practical answer, not a language-lawyer answer. I'm aware that defining new members in namespace std
is technically undefined, but in practice that won't cause issues on any compiler so I don't find that worth worrying about for the purposes of this question. I'm worried about cases such as accidental dangling references and the like.)
المحلول 2
نصائح أخرى
move
does not move, but your move
does. This means that in C++11 a std::move
on an expression that does not assign it anywhere, or the consumer does not modify data. Yours does.
What is worse is that your move
blocks rvo/nrvo, just like C++11. In C++11 your return statement from test
would be a bad idea, as the return value would be implicitly move
ed. In C++03, as nrvo is blocked on arguments, it would be optimal. So the use of the two is different.
Your std::move
return value experiences reference lifetime extension, while the return value in C++11 does not. Code will have to be fully tested in both.