Skip to content

Commit e6ce1d8

Browse files
authored
Merge pull request #1219 from p00ya/string-move-semantics
Avoid copies in String
2 parents 2c3b7d8 + 99e1814 commit e6ce1d8

File tree

5 files changed

+128
-13
lines changed

5 files changed

+128
-13
lines changed

ChangeLog

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

315
* 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: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
## Copyright (C) 2012 - 2019 Dirk Eddelbuettel and Romain Francois
2+
## Copyright (C) 2012 - 2022 Dirk Eddelbuettel and Romain Francois
33
##
44
## This file is part of Rcpp.
55
##
@@ -63,6 +63,24 @@ expect_equal( res, target )
6363
res <- test_ctor("abc")
6464
expect_identical(res, "abc")
6565

66+
if (Rcpp:::capabilities()[["Full C++11 support"]]) {
67+
## test.String.move.ctor <- function() {
68+
res <- test_move_ctor()
69+
expect_identical(res, c("", "test"))
70+
71+
## test.String.move.std.string.ctor <- function() {
72+
res <- test_move_std_string_ctor()
73+
expect_identical(res, "test")
74+
75+
## test.String.move.assignment <- function() {
76+
res <- test_move_assignment()
77+
expect_identical(res, c("", "test"))
78+
79+
## test.String.move.std.string.assignment <- function() {
80+
res <- test_move_std_string_assignment()
81+
expect_identical(res, "test")
82+
}
83+
6684
# test.push.front <- function() {
6785
res <- test_push_front("def")
6886
expect_identical(res, "abcdef")

0 commit comments

Comments
 (0)