Skip to content

Commit ce51055

Browse files
committed
Avoid copies in String
Add move constructor/assignment for String&& and std::string&&, and attempt to preserve the buffer/SEXP representation when copying in the existing copy ctor and assignment operators. Closes #1218.
1 parent 2c3b7d8 commit ce51055

File tree

5 files changed

+122
-12
lines changed

5 files changed

+122
-12
lines changed

ChangeLog

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
2018-10-25 Dean Scarff <[email protected]>
2+
3+
* inst/include/Rcpp/String.h: Make less copies of strings by adding
4+
move semantics and preserving the buffer/SEXP representation when
5+
copying
6+
* inst/include/RcppCommon.h: include <utility> for std::move
7+
* inst/tinytest/cpp/String.cpp: Add unit tests
8+
* inst/tinytest/test_string.R: Add unit tests
9+
110
2022-05-12 Dirk Eddelbuettel <[email protected]>
211

312
* docker/ci/Dockerfile: Added xml2, also use more r-cran-* binaries

inst/include/Rcpp/String.h

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,11 @@ namespace Rcpp {
5858
}
5959

6060
/** copy constructor */
61-
String(const String& other) : data(other.get_sexp()), token(R_NilValue), valid(true), buffer_ready(false), enc(Rf_getCharCE(other.get_sexp())) {
62-
token = Rcpp_PreciousPreserve(data);
61+
String(const String& s) : data(R_NilValue), token(R_NilValue), buffer(s.buffer), valid(s.valid), buffer_ready(s.buffer_ready), enc(s.enc) {
62+
if (!buffer_ready) {
63+
data = s.get_sexp();
64+
token = Rcpp_PreciousPreserve(data);
65+
}
6366
RCPP_STRING_DEBUG("String(const String&)");
6467
}
6568

@@ -111,12 +114,27 @@ namespace Rcpp {
111114
}
112115

113116
/** from a std::string */
114-
String(const std::string& s, cetype_t enc = CE_UTF8) : buffer(s), valid(false), buffer_ready(true), enc(enc) {
115-
data = R_NilValue;
116-
token = R_NilValue;
117+
String(const std::string& s, cetype_t enc = CE_UTF8) : data(R_NilValue), token(R_NilValue), buffer(s), valid(false), buffer_ready(true), enc(enc) {
117118
RCPP_STRING_DEBUG("String(const std::string&, cetype_t)");
118119
}
120+
#ifdef RCPP_USING_CXX11
121+
/** move constructor */
122+
String(String&& s) : data(s.data), token(s.token), buffer(std::move(s.buffer)), valid(s.valid), buffer_ready(s.buffer_ready), enc(s.enc) {
123+
// Erase s.
124+
s.data = R_NilValue;
125+
s.token = R_NilValue;
126+
s.buffer = std::string();
127+
s.valid = false;
128+
s.buffer_ready = true;
129+
s.enc = CE_UTF8;
130+
RCPP_STRING_DEBUG("String(String&&)");
131+
}
119132

133+
/** move a std::string */
134+
String(std::string&& s, cetype_t enc = CE_UTF8) : data(R_NilValue), token(R_NilValue), buffer(s), valid(false), buffer_ready(true), enc(enc) {
135+
RCPP_STRING_DEBUG("String(std::string&&, cetype_t)");
136+
}
137+
#endif
120138
String(const std::wstring& s, cetype_t enc = CE_UTF8) : data(internal::make_charsexp(s)), token(R_NilValue), valid(true), buffer_ready(false), enc(enc) {
121139
token = Rcpp_PreciousPreserve(data);
122140
RCPP_STRING_DEBUG("String(const std::wstring&, cetype_t)");
@@ -220,14 +238,27 @@ namespace Rcpp {
220238
return *this;
221239
}
222240
inline String& operator=(const String& other) {
223-
SEXP x = other.get_sexp();
224-
if (data != x) {
225-
data = x;
226-
Rcpp_PreciousRelease(token);
227-
token = Rcpp_PreciousPreserve(x);
241+
if (other.buffer_ready) {
242+
// Copy the buffer without creating a SEXP.
243+
if (valid) {
244+
Rcpp_PreciousRelease(token);
245+
valid = false;
246+
}
247+
data = R_NilValue;
248+
token = R_NilValue;
249+
buffer = other.buffer;
250+
buffer_ready = true;
251+
enc = other.enc;
252+
} else {
253+
SEXP x = other.get_sexp();
254+
if (data != x) {
255+
data = x;
256+
Rcpp_PreciousRelease(token);
257+
token = Rcpp_PreciousPreserve(x);
258+
}
259+
valid = true;
260+
buffer_ready = false;
228261
}
229-
valid = true;
230-
buffer_ready = false;
231262
return *this;
232263
}
233264
inline String& operator=(const std::string& s) {
@@ -236,6 +267,30 @@ namespace Rcpp {
236267
buffer_ready = true;
237268
return *this;
238269
}
270+
#ifdef RCPP_USING_CXX11
271+
inline String& operator=(String&& other) {
272+
data = other.data;
273+
token = other.token;
274+
buffer = std::move(other.buffer);
275+
valid = other.valid;
276+
buffer_ready = other.buffer_ready;
277+
enc = other.enc;
278+
// Erase other.
279+
other.data = R_NilValue;
280+
other.token = R_NilValue;
281+
other.buffer = std::string();
282+
other.valid = false;
283+
other.buffer_ready = true;
284+
other.enc = CE_UTF8;
285+
return *this;
286+
}
287+
inline String& operator=(std::string&& s) {
288+
buffer = s;
289+
valid = false;
290+
buffer_ready = true;
291+
return *this;
292+
}
293+
#endif
239294
inline String& operator=(const char* s) {
240295
buffer = s;
241296
valid = false;

inst/include/RcppCommon.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ namespace Rcpp {
6666
#include <cfloat>
6767
#include <limits>
6868
#include <typeinfo>
69+
#include <utility>
6970
#include <Rcpp/sprintf.h>
7071
#include <R_ext/Callbacks.h>
7172
#include <R_ext/Visibility.h>

inst/tinytest/cpp/String.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,35 @@ String test_ctor(String x) {
5353
return test;
5454
}
5555

56+
// [[Rcpp::export]]
57+
CharacterVector test_move_ctor() {
58+
std::vector<String> v = { "test" };
59+
String t = std::move( v[0] );
60+
v.push_back( std::move( t ) );
61+
return CharacterVector( v.begin(), v.end() );
62+
}
63+
64+
// [[Rcpp::export]]
65+
String test_move_std_string_ctor() {
66+
std::string s = "test";
67+
return String( std::move( s ) );
68+
}
69+
70+
// [[Rcpp::export]]
71+
CharacterVector test_move_assignment() {
72+
std::vector<String> v = { "test", "abc" };
73+
v[1] = std::move( v[0] );
74+
return CharacterVector( v.begin(), v.end() );
75+
}
76+
77+
// [[Rcpp::export]]
78+
String test_move_std_string_assignment() {
79+
String x = "abc";
80+
std::string s = "test";
81+
x = std::move( s );
82+
return x;
83+
}
84+
5685
// [[Rcpp::export]]
5786
List test_compare_Strings( String aa, String bb ){
5887
return List::create(

inst/tinytest/test_string.R

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,22 @@ expect_equal( res, target )
6363
res <- test_ctor("abc")
6464
expect_identical(res, "abc")
6565

66+
# test.String.move.ctor <- function() {
67+
res <- test_move_ctor()
68+
expect_identical(res, c("", "test"))
69+
70+
# test.String.move.std.string.ctor <- function() {
71+
res <- test_move_std_string_ctor()
72+
expect_identical(res, "test")
73+
74+
# test.String.move.assignment <- function() {
75+
res <- test_move_assignment()
76+
expect_identical(res, c("", "test"))
77+
78+
# test.String.move.std.string.assignment <- function() {
79+
res <- test_move_std_string_assignment()
80+
expect_identical(res, "test")
81+
6682
# test.push.front <- function() {
6783
res <- test_push_front("def")
6884
expect_identical(res, "abcdef")

0 commit comments

Comments
 (0)