RFC: Allow moved-from strings to be non-empty
Jonathan Wakely
jwakely@redhat.com
Thu Oct 25 21:53:00 GMT 2018
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.
-------------- next part --------------
commit ca3fa69d347c62901eb826f208ec530ea9c70fe7
Author: Jonathan Wakely <jwakely@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");
More information about the Libstdc++
mailing list