Which String Parameter Type? Which String Parameter? String Parameters
Summary
If You… | Use Parameter Type |
---|---|
always need a copy of the input string inside the function | std::string
pass by value |
want read-only access
|
#include <string_view>
std::string_view |
want read-only access
|
std::string const&
pass by const reference |
want the function to modify the input string in-place
(you should try to avoid such output parameters) |
std::string &
pass by (non-const) reference |
Pass By Value!
By value ⇒
One copy per call
Box by_value (std::string id) {
// sanitize id (already a copy)
replace(begin(id), end(id),
' ', '-');
return Box{std::move(id)};
}
By const reference ⇒
Sometimes two copies
Box by_cref (std::string const& id) {
// make copy for replacing
std::string idCopy {id};
replace(begin(idCopy), end(idCopy),
' ', '-');
return Box{std::move(idCopy)};
}
Call with string literal: by value ⇒ less copies
Box b = by_value("A 2");
implicitly creates one string object:
- function parameter
string id{"A 2"}
Box b = by_cref("A 2");
creates two string objects:
- implicit
string{"A 2"}
that is bound to function parameterstring const& id
- explicit local copy
idCopy
Call with string Rvalue: by value ⇒ less copies
Box b = by_value(std::string{myId});
does not create an additional string:
- temporary string object is moved into function parameter
id
Box b = by_cref(std::string{myId});
creates one additional string object:
- explicit local copy
idCopy
Call with string Lvalue: no difference
std::string myId = "K 9";
Box b = by_value(myId);
- creates one additional string object:
- function parameter
string id{myId}
as copy ofmyId
.
Box b = by_cref(myId);
creates one additional string object:
- explicit local copy
idCopy
C++17
Use std::string_view
C++17
- lightweight (= cheap to copy, can be passed by value)
- non-owning (= not responsible for allocating or deleting memory)
- read-only view
- of a character range or string(-like) object (
std::string
/"literal"
/ …)
#include <string>
#include <string_view>
class Pattern { …
public: …
auto match (std::string_view s) const { … }
};
Pattern p {"ab.*"};
auto r1 = p.match("abx"); // no copy
std::string txt = "abcde";
auto r2 = p.match(txt); // no copy
We don't want/expect additional copies or memory allocations for a read-only parameter!
The traditional choice std::string const&
is problematic:
- A
std::string
can be constructed from string literals or an iterator range (to achar
sequence). - If we pass an object as function argument that is not a string itself, but something that can be used to construct a string, e.g., a string literal or an iterator range, a new temporary string object will be allocated and bound to the const reference.
#include <vector>
#include <string>
#include <string_view>
void f_cref (std::string const& s) { … }
void f_view (std::string_view s) { … }
int main () {
std::string stdStr = "Standard String";
auto const cStr = "C-String";
std::vector<char> v {'c','h','a','r','s'};
f_cref(stdStr); // no copy
f_cref(cStr); // temp copy
f_cref("Literal"); // temp copy
f_cref({begin(v),end(v)}); // temp copy
f_view(stdStr); // no copy
f_view(cStr); // no copy
f_view("Literal"); // no copy
f_view({begin(v),end(v)}); // no copy
}
by avoiding a level of indirection:
C++98-14
Use 2 Overloads: string const&
and char const*
to avoid temporary copies when string literals are used as input
- works in all C++ standards
char const*
avoids unnecessary copies when literals are passed- more code
- error-prone C-style pointer interface
#include <string>
class Pattern { …
public: …
// takes string objects
auto match (std::string const& s) const {
return match(s.data()); // relay to 2nd overload
}
// takes string literals / C-strings
auto match (char const* s) const { … }
};
Pattern p {"ab.*"};
auto r1 = p.match("abx"); // no copy
std::string txt = "abcde";
auto r2 = p.match(txt); // no copy
In-Place Modification Of Input String In-Place Modification Out Parameter
#include <iostream>
#include <string>
void replace_umlauts (std::string& s) { … }
void test_replace_umlauts () {
std::string s = "Ölüberschussländer";
replace_umlauts(s);
assert(s == "Oelueberschusslaender");
}
You should avoid output parameters
for in-place modification
in general:
- side effects (here: change of string) not obvious at the call site
- harder to reason about correctness
- harder to use such functions in parallel programs
However, sometimes reference parameters can be beneficial for avoiding excessive copies / memory allocations by re-using the same object's memory multiple times.
Comments…