When an SSO string is contained in the small string buffer or has an unequal allocator a move operation performs a copy, leaving the original data in the moved-from string. Setting the length of the moved-from string to zero is not required, so we can avoid two writes (to the length member and to set the first character to nul) by leaving the moved-from string unchanged.
This might be surprising to some users, who (wrongly) expect a string to always be empty after a move. Is that acceptable? It's worth noting that the current behaviour, where we do more work than required, also surprises some people, e.g. https://stackoverflow.com/q/52696413/981959 Some tests need to be adjusted due to the new behaviour, but they have comments saying they're testing specific implementation details, so I don't think needing to adjust them is an argument against making the change: // NOTE: This makes use of the fact that we know how moveable // is implemented on string (via swap). If the implementation changed // this test may begin to fail. Should we make this change? * include/bits/basic_string.h [_GLIBCXX_USE_CXX11_ABI] (basic_string::basic_string(basic_string&&)): Only set length of source string to zero when allocated storage is transferred. (basic_string::operator=(basic_string&&)): Likewise. Split separate cases into separate conditionals. * testsuite/21_strings/basic_string/cons/char/moveable.cc: Adjust expected state of moved-from strings. * testsuite/21_strings/basic_string/cons/char/moveable2.cc: Likewise. * testsuite/21_strings/basic_string/cons/char/moveable2_c++17.cc: Likewise. * testsuite/21_strings/basic_string/cons/wchar_t/moveable.cc: Likewise. * testsuite/21_strings/basic_string/cons/wchar_t/moveable2.cc: Likewise. * testsuite/21_strings/basic_string/cons/wchar_t/moveable2_c++17.cc: Likewise. * testsuite/21_strings/basic_string/modifiers/assign/char/ move_assign.cc: Likewise. * testsuite/21_strings/basic_string/modifiers/assign/wchar_t/ move_assign.cc: Likewise.
commit ca3fa69d347c62901eb826f208ec530ea9c70fe7 Author: Jonathan Wakely <jwak...@redhat.com> Date: Thu Oct 25 20:05:12 2018 +0100 Allow moved-from strings to be non-empty When an SSO string is contained in the small string buffer or has an unequal allocator a move operation performs a copy, leaving the original data in the moved-from string. Setting the length of the moved-from string to zero is not required, so we can avoid two writes (to the length member and to set the first character to nul). * include/bits/basic_string.h [_GLIBCXX_USE_CXX11_ABI] (basic_string::basic_string(basic_string&&)): Only set length of source string to zero when allocated storage is transferred. (basic_string::operator=(basic_string&&)): Likewise. Split separate cases into separate conditionals. * testsuite/21_strings/basic_string/cons/char/moveable.cc: Adjust expected state of moved-from strings. * testsuite/21_strings/basic_string/cons/char/moveable2.cc: Likewise. * testsuite/21_strings/basic_string/cons/char/moveable2_c++17.cc: Likewise. * testsuite/21_strings/basic_string/cons/wchar_t/moveable.cc: Likewise. * testsuite/21_strings/basic_string/cons/wchar_t/moveable2.cc: Likewise. * testsuite/21_strings/basic_string/cons/wchar_t/moveable2_c++17.cc: Likewise. * testsuite/21_strings/basic_string/modifiers/assign/char/ move_assign.cc: Likewise. * testsuite/21_strings/basic_string/modifiers/assign/wchar_t/ move_assign.cc: Likewise. diff --git a/libstdc++-v3/include/bits/basic_string.h b/libstdc++-v3/include/bits/basic_string.h index ae6530fcdc9..842e5f012f1 100644 --- a/libstdc++-v3/include/bits/basic_string.h +++ b/libstdc++-v3/include/bits/basic_string.h @@ -542,6 +542,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 basic_string(basic_string&& __str) noexcept : _M_dataplus(_M_local_data(), std::move(__str._M_get_allocator())) { + // Must use _M_length() here not _M_set_length() because + // basic_stringbuf relies on writing into unallocated capacity so + // we mess up the contents if we put a '\0' in the string. + _M_length(__str.length()); + if (__str._M_is_local()) { traits_type::copy(_M_local_buf, __str._M_local_buf, @@ -551,14 +556,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 { _M_data(__str._M_data()); _M_capacity(__str._M_allocated_capacity); + __str._M_data(__str._M_local_data()); + __str._M_set_length(0); } - - // Must use _M_length() here not _M_set_length() because - // basic_stringbuf relies on writing into unallocated capacity so - // we mess up the contents if we put a '\0' in the string. - _M_length(__str.length()); - __str._M_data(__str._M_local_data()); - __str._M_set_length(0); } /** @@ -746,7 +746,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 if (__str._M_is_local()) { - // We've always got room for a short string, just copy it. + // We've always got room for a small string, just copy it. if (__str.size()) this->_S_copy(_M_data(), __str._M_data(), __str.size()); _M_set_length(__str.size()); @@ -756,34 +756,38 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 || _M_get_allocator() == __str._M_get_allocator()) { // Just move the allocated pointer, our allocator can free it. - pointer __data = nullptr; - size_type __capacity; - if (!_M_is_local()) + if (_M_is_local()) { - if (_Alloc_traits::_S_always_equal()) - { - // __str can reuse our existing storage. - __data = _M_data(); - __capacity = _M_allocated_capacity; - } - else // __str can't use it, so free it. - _M_destroy(_M_allocated_capacity); + _M_data(__str._M_data()); + _M_length(__str.length()); + _M_capacity(__str._M_allocated_capacity); + __str._M_data(__str._M_local_buf); } - - _M_data(__str._M_data()); - _M_length(__str.length()); - _M_capacity(__str._M_allocated_capacity); - if (__data) + else if (_Alloc_traits::_S_always_equal() + || _M_get_allocator() == __str._M_get_allocator()) { + // __str can reuse our existing storage. + pointer __data = _M_data(); + size_type __capacity = _M_allocated_capacity; + _M_data(__str._M_data()); + _M_length(__str.length()); + _M_capacity(__str._M_allocated_capacity); __str._M_data(__data); __str._M_capacity(__capacity); } else - __str._M_data(__str._M_local_buf); + { + // __str can't use it, so free it. + _M_destroy(_M_allocated_capacity); + _M_data(__str._M_data()); + _M_length(__str.length()); + _M_capacity(__str._M_allocated_capacity); + __str._M_data(__str._M_local_buf); + } + __str.clear(); } else // Need to do a deep copy assign(__str); - __str.clear(); return *this; } diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/moveable.cc b/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/moveable.cc index 21932cf7736..e19cd85925b 100644 --- a/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/moveable.cc +++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/moveable.cc @@ -1,5 +1,4 @@ // { dg-do run { target c++11 } } -// { dg-require-string-conversions "" } // Copyright (C) 2010-2018 Free Software Foundation, Inc. // @@ -18,10 +17,6 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. -// NOTE: This makes use of the fact that we know how moveable -// is implemented on string (via swap). If the implementation changed -// this test may begin to fail. - #include <string> #include <utility> #include <testsuite_hooks.h> @@ -31,11 +26,30 @@ void test01() std::string a, b; a.push_back('1'); b = std::move(a); - VERIFY( b.size() == 1 && b[0] == '1' && a.size() == 0 ); + VERIFY( b.size() == 1 && b[0] == '1' ); + // a.size() is unspecified after a move +#if ! _GLIBCXX_USE_CXX11_ABI + VERIFY( a.size() == 0 ); +#else + a.clear(); +#endif + + a.reserve(200); // defeat small string optimization + a.push_back('1'); + b = std::move(a); + VERIFY( b.size() == 1 && b[0] == '1' ); + VERIFY( a.size() == 0 ); // size is unspecified, but true for our string std::string c(std::move(b)); VERIFY( c.size() == 1 && c[0] == '1' ); + VERIFY( b.size() == 0 ); // size is unspecified, but true for our string + + b.push_back('1'); // for SSO string this uses local buffer + std::string d(std::move(b)); + VERIFY( d.size() == 1 && d[0] == '1' ); +#if ! _GLIBCXX_USE_CXX11_ABI VERIFY( b.size() == 0 ); +#endif } int main() diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/moveable2.cc b/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/moveable2.cc index 8488b9698ff..cb4c7f2e39e 100644 --- a/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/moveable2.cc +++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/moveable2.cc @@ -19,10 +19,6 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. -// NOTE: This makes use of the fact that we know how moveable -// is implemented on string (via swap). If the implementation changed -// this test may begin to fail. - #include <string> #include <utility> #include <testsuite_hooks.h> @@ -40,11 +36,18 @@ void test01() tstring a, b; a.push_back('1'); b = std::move(a); - VERIFY( b.size() == 1 && b[0] == '1' && a.size() == 0 ); + VERIFY( b.size() == 1 && b[0] == '1' ); + // a.size() is unspecified after a move +#if ! _GLIBCXX_USE_CXX11_ABI + VERIFY( a.size() == 0 ); +#endif tstring c(std::move(b)); VERIFY( c.size() == 1 && c[0] == '1' ); + // b.size() is unspecified after a move +#if ! _GLIBCXX_USE_CXX11_ABI VERIFY( b.size() == 0 ); +#endif } int main() diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/moveable2_c++17.cc b/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/moveable2_c++17.cc index a6e2ebcade0..706605c5c20 100644 --- a/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/moveable2_c++17.cc +++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/moveable2_c++17.cc @@ -18,10 +18,6 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. -// NOTE: This makes use of the fact that we know how moveable -// is implemented on string (via swap). If the implementation changed -// this test may begin to fail. - #include <string> #include <utility> #include <testsuite_hooks.h> @@ -39,11 +35,18 @@ void test01() tstring a, b; a.push_back('1'); b = std::move(a); - VERIFY( b.size() == 1 && b[0] == '1' && a.size() == 0 ); + VERIFY( b.size() == 1 && b[0] == '1' ); + // a.size() is unspecified after a move +#if ! _GLIBCXX_USE_CXX11_ABI + VERIFY( a.size() == 0 ); +#endif tstring c(std::move(b)); VERIFY( c.size() == 1 && c[0] == '1' ); + // b.size() is unspecified after a move +#if ! _GLIBCXX_USE_CXX11_ABI VERIFY( b.size() == 0 ); +#endif } int main() diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/moveable.cc b/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/moveable.cc index aa6a52749dc..b7ba4865b16 100644 --- a/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/moveable.cc +++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/moveable.cc @@ -1,5 +1,4 @@ // { dg-do run { target c++11 } } -// { dg-require-string-conversions "" } // Copyright (C) 2010-2018 Free Software Foundation, Inc. // @@ -18,10 +17,6 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. -// NOTE: This makes use of the fact that we know how moveable -// is implemented on string (via swap). If the implementation changed -// this test may begin to fail. - #include <string> #include <utility> #include <testsuite_hooks.h> @@ -31,11 +26,30 @@ void test01() std::wstring a, b; a.push_back(L'1'); b = std::move(a); - VERIFY( b.size() == 1 && b[0] == L'1' && a.size() == 0 ); + VERIFY( b.size() == 1 && b[0] == L'1' ); + // a.size() is unspecified after a move +#if ! _GLIBCXX_USE_CXX11_ABI + VERIFY( a.size() == 0 ); +#else + a.clear(); +#endif + + a.reserve(200); // defeat small string optimization + a.push_back(L'1'); + b = std::move(a); + VERIFY( b.size() == 1 && b[0] == L'1' ); + VERIFY( a.size() == 0 ); // size is unspecified, but true for our string std::wstring c(std::move(b)); VERIFY( c.size() == 1 && c[0] == L'1' ); VERIFY( b.size() == 0 ); + + b.push_back(L'1'); // for SSO string this uses local buffer + std::wstring d(std::move(b)); + VERIFY( d.size() == 1 && d[0] == L'1' ); +#if ! _GLIBCXX_USE_CXX11_ABI + VERIFY( b.size() == 0 ); +#endif } int main() diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/moveable2.cc b/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/moveable2.cc index 389adc5205d..61d26d85081 100644 --- a/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/moveable2.cc +++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/moveable2.cc @@ -1,6 +1,5 @@ // { dg-options "-fno-inline" } // { dg-do run { target c++11 } } -// { dg-require-string-conversions "" } // Copyright (C) 2011-2018 Free Software Foundation, Inc. // @@ -19,10 +18,6 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. -// NOTE: This makes use of the fact that we know how moveable -// is implemented on string (via swap). If the implementation changed -// this test may begin to fail. - #include <string> #include <utility> #include <testsuite_hooks.h> @@ -40,11 +35,18 @@ void test01() twstring a, b; a.push_back(L'1'); b = std::move(a); - VERIFY( b.size() == 1 && b[0] == L'1' && a.size() == 0 ); + VERIFY( b.size() == 1 && b[0] == '1' ); + // a.size() is unspecified after a move +#if ! _GLIBCXX_USE_CXX11_ABI + VERIFY( a.size() == 0 ); +#endif twstring c(std::move(b)); VERIFY( c.size() == 1 && c[0] == L'1' ); + // b.size() is unspecified after a move +#if ! _GLIBCXX_USE_CXX11_ABI VERIFY( b.size() == 0 ); +#endif } int main() diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/moveable2_c++17.cc b/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/moveable2_c++17.cc index 3c05a48dd7d..cda7e0f6b96 100644 --- a/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/moveable2_c++17.cc +++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/moveable2_c++17.cc @@ -18,10 +18,6 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. -// NOTE: This makes use of the fact that we know how moveable -// is implemented on string (via swap). If the implementation changed -// this test may begin to fail. - #include <string> #include <utility> #include <testsuite_hooks.h> @@ -39,11 +35,18 @@ void test01() tstring a, b; a.push_back(L'1'); b = std::move(a); - VERIFY( b.size() == 1 && b[0] == L'1' && a.size() == 0 ); + VERIFY( b.size() == 1 && b[0] == L'1' ); + // a.size() is unspecified after a move +#if ! _GLIBCXX_USE_CXX11_ABI + VERIFY( a.size() == 0 ); +#endif tstring c(std::move(b)); VERIFY( c.size() == 1 && c[0] == L'1' ); + // b.size() is unspecified after a move +#if ! _GLIBCXX_USE_CXX11_ABI VERIFY( b.size() == 0 ); +#endif } int main() diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/assign/char/move_assign.cc b/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/assign/char/move_assign.cc index 7089fea04c2..4fbf6bab1cb 100644 --- a/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/assign/char/move_assign.cc +++ b/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/assign/char/move_assign.cc @@ -1,5 +1,4 @@ // { dg-do run { target c++11 } } -// { dg-require-string-conversions "" } // Copyright (C) 2010-2018 Free Software Foundation, Inc. // @@ -18,10 +17,6 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. -// NOTE: This makes use of the fact that we know how moveable -// is implemented on string (via swap). If the implementation changes -// this test may begin to fail. - #include <string> #include <utility> #include <testsuite_hooks.h> @@ -31,7 +26,11 @@ void test01() std::string a, b; a.push_back('1'); b.assign(std::move(a)); - VERIFY( b.size() == 1 && b[0] == '1' && a.size() == 0 ); + VERIFY( b.size() == 1 && b[0] == '1' ); + // a.size() is unspecified after a move +#if ! _GLIBCXX_USE_CXX11_ABI + VERIFY( a.size() == 0 ); +#endif // True for std::allocator because is_always_equal, but not true in general: static_assert(noexcept(a.assign(std::move(b))), "lwg 2063"); diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/assign/wchar_t/move_assign.cc b/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/assign/wchar_t/move_assign.cc index 8d394602a9f..e0550c4f283 100644 --- a/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/assign/wchar_t/move_assign.cc +++ b/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/assign/wchar_t/move_assign.cc @@ -1,5 +1,4 @@ // { dg-do run { target c++11 } } -// { dg-require-string-conversions "" } // Copyright (C) 2010-2018 Free Software Foundation, Inc. // @@ -18,10 +17,6 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. -// NOTE: This makes use of the fact that we know how moveable -// is implemented on string (via swap). If the implementation changes -// this test may begin to fail. - #include <string> #include <utility> #include <testsuite_hooks.h> @@ -31,7 +26,11 @@ void test01() std::wstring a, b; a.push_back(L'1'); b.assign(std::move(a)); - VERIFY( b.size() == 1 && b[0] == '1' && a.size() == 0 ); + VERIFY( b.size() == 1 && b[0] == L'1' ); + // a.size() is unspecified after a move +#if ! _GLIBCXX_USE_CXX11_ABI + VERIFY( a.size() == 0 ); +#endif // True for std::allocator because is_always_equal, but not true in general: static_assert(noexcept(a.assign(std::move(b))), "lwg 2063");