Passing Std::String by Value or Reference

Passing std::string by Value or Reference

There are multiple answers based on what you are doing with the string.

1) Using the string as an id (will not be modified). Passing it in by const reference is probably the best idea here: (std::string const&)

2) Modifying the string but not wanting the caller to see that change. Passing it in by value is preferable: (std::string)

3) Modifying the string but wanting the caller to see that change. Passing it in by reference is preferable: (std::string &)

4) Sending the string into the function and the caller of the function will never use the string again. Using move semantics might be an option (std::string &&)

Are the days of passing const std::string & as a parameter over?

The reason Herb said what he said is because of cases like this.

Let's say I have function A which calls function B, which calls function C. And A passes a string through B and into C. A does not know or care about C; all A knows about is B. That is, C is an implementation detail of B.

Let's say that A is defined as follows:

void A()
{
B("value");
}

If B and C take the string by const&, then it looks something like this:

void B(const std::string &str)
{
C(str);
}

void C(const std::string &str)
{
//Do something with `str`. Does not store it.
}

All well and good. You're just passing pointers around, no copying, no moving, everyone's happy. C takes a const& because it doesn't store the string. It simply uses it.

Now, I want to make one simple change: C needs to store the string somewhere.

void C(const std::string &str)
{
//Do something with `str`.
m_str = str;
}

Hello, copy constructor and potential memory allocation (ignore the Short String Optimization (SSO)). C++11's move semantics are supposed to make it possible to remove needless copy-constructing, right? And A passes a temporary; there's no reason why C should have to copy the data. It should just abscond with what was given to it.

Except it can't. Because it takes a const&.

If I change C to take its parameter by value, that just causes B to do the copy into that parameter; I gain nothing.

So if I had just passed str by value through all of the functions, relying on std::move to shuffle the data around, we wouldn't have this problem. If someone wants to hold on to it, they can. If they don't, oh well.

Is it more expensive? Yes; moving into a value is more expensive than using references. Is it less expensive than the copy? Not for small strings with SSO. Is it worth doing?

It depends on your use case. How much do you hate memory allocations?

Passing C++ strings by value or by reference

No. Pass it by reference:

void foo(const std::string& pString);

In general, pass things by-reference if they have a non-trivial copy-constructor, otherwise by-value.

A string usually consists of a pointer to data, and a length counter. It may contain more or less, since it's implementation defined, but it's highly unlikely your implementation only uses one pointer.

In template code, you may as well use const T&, since the definition of the function will be available to the compiler. This means it can decide if it should be a reference or not for you. (I think)

Passing a string by value, reference and rvalue

Let's analyze your code and suppose long strings (without applied SSO):

void add(std::string msg) {
msg += "world";
}

void StringCreation() {
add(std::string("hello "));
}

Here, a converting constructor (ConvC) from the string literal is called first to initialize the temporary std::string("hello "). This temporary (an rvalue) is then used to initialize the parameter msg by the move constructor (MC). However, the latter is very likely optimized away by copy elision. Finally, the operator += is called. Bottom line: 1x ConvC and 1x +=.

void StringCopy() {
std::string msg("hello ");
add(msg);
}

Here, the parameter msg is copy-initialized (by copy constructor - CC) by the lvalue argument msg. Bottom line: 1x ConvC, 1x CC, and 1x +=. In case of long strings, this is the slowest version, since copy involves dynamic memory allocations (the only case).

void StringMove() {
std::string msg("hello ");
add(std::move(msg));
}

Why is this slower than StringCreation? Simply because there is an additional MC involved that initializes the parameter msg. It cannot be elided, since the object msg still exist after the call of add. Just it is moved-from. Bottom line: 1x ConvC, 1x MC, 1x +=.

void addRef(std::string& msg) {
msg += "world";
}

void StringReference() {
std::string msg("hello ");
addRef(msg);
}

Here, the operator += is applied to the referenced object, so there is no reason for any copy/move. Bottom line: 1x ConvC, 1x +=. Same time as for StringCreation.

void addRvalue(std::string&& msg) {
msg += "world";
}

void StringRvalue() {
std::string msg("hello ");
addRvalue(std::move(msg));
}

With Clang, the time is same as for StringReference. With GCC, the time is same as for StringMove. In fact, I don't have an explanation for this behavior for now. (It seems to me that GCC is creating some additional temporary initialized by MC. However, I don't know why.)

Should I pass a string by value or pass a pointer to it?

It depends on what you want the behavior of the code to be.

Most of the suggested answers here change the behavior from your posted code in that they will modify the string that's passed in (or make the code fail to compile because it can't modify the passed in argument).

In the example you posted,the string passed to myclass::setVersion() will not be modified (the parameter may be modified, but that is just a copy fo the string passed in; a copy which will be destroyed when the function returns).

For a a case like this, I'd suggest passing a const std::string&:

int myclass::setVersion(std::string const& ver)
{
if (ver.size()>1)
{
version = ver;
return 0;
}
else
return -1;
}

This way the copy is made only when necessary.

But honestly, unless the function is called often, it's probably nothing to worry much about.

Passing strings by reference and value in C++

Your code works as expected.

&filename returns the memory address of (aka a pointer to) filename, but startup(std::string& name) wants a reference, not a pointer.

References in C++ are simply passed with the normal "pass-by-value" syntax:

startup(filename) takes filename by reference.


If you modified the startup function to take a pointer to an std::string instead:

void startup(std::string* name)

then you would pass it using the address-of operator:

startup(&filename)


As a side note, you should also make the outputfile function take its parameter by reference, because there's no need to copy the string. And since you're not modifying the parameter, you should take it as a const reference:

void outputfile(const std::string& name)

For more info, here are the rules of thumb for C++ regarding how to pass function parameters.

Advantages of pass-by-value and std::move over pass-by-reference

  1. Did I understand correctly what is happening here?

Yes.


  1. Is there any upside of using std::move over passing by reference and just calling m_name{name}?

An easy to grasp function signature without any additional overloads. The signature immediately reveals that the argument will be copied - this saves callers from wondering whether a const std::string& reference might be stored as a data member, possibly becoming a dangling reference later on. And there is no need to overload on std::string&& name and const std::string& arguments to avoid unnecessary copies when rvalues are passed to the function. Passing an lvalue

std::string nameString("Alex");
Creature c(nameString);

to the function that takes its argument by value causes one copy and one move construction. Passing an rvalue to the same function

std::string nameString("Alex");
Creature c(std::move(nameString));

causes two move constructions. In contrast, when the function parameter is const std::string&, there will always be a copy, even when passing an rvalue argument. This is clearly an advantage as long as the argument type is cheap to move-construct (this is the case for std::string).

But there is a downside to consider: the reasoning doesn't work for functions that assign the function argument to another variable (instead of initializing it):

void setName(std::string name)
{
m_name = std::move(name);
}

will cause a deallocation of the resource that m_name refers to before it's reassigned. I recommend reading Item 41 in Effective Modern C++ and also this question.

How to pass std::string_view by value or by const reference

We usually pass string_views by value.

Examples from the C++20 draft:

  • Formatting
  • Time Zone Lookup

Pass string class variable by reference in C++

How to pass string by reference in C++?

You simply add & to the parameter.

Are strings a normal array?

No, std::string is not an array.



Related Topics



Leave a reply



Submit