Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is it not a good idea to pass by reference?

This is a memory allocation issue that I've never really understood.

void unleashMonkeyFish()  
{  
    MonkeyFish * monkey_fish = new MonkeyFish();
    std::string localname = "Wanda";  
    monkey_fish->setName(localname);  
    monkey_fish->go();  
}  

In the above code, I've created a MonkeyFish object on the heap, assigned it a name, and then unleashed it upon the world. Let's say that ownership of the allocated memory has been transferred to the MonkeyFish object itself - and only the MonkeyFish itself will decide when to die and delete itself.

Now, when I define the "name" data member inside the MonkeyFish class, I can choose one of the following:

std::string name;
std::string & name;

When I define the prototype for the setName() function inside the MonkeyFish class, I can choose one of the following:

void setName( const std::string & parameter_name );
void setName( const std::string parameter_name );

I want to be able to minimize string copies. In fact, I want to eliminate them entirely if I can. So, it seems like I should pass the parameter by reference...right?

What bugs me is that it seems that my localname variable is going to go out of scope once the unleashMonkeyFish() function completes. Does that mean I'm FORCED to pass the parameter by copy? Or can I pass it by reference and "get away with it" somehow?

Basically, I want to avoid these scenarios:

  1. I don't want to set the MonkeyFish's name, only to have the memory for the localname string go away when the unleashMonkeyFish() function terminates. (This seems like it would be very bad.)
  2. I don't want to copy the string if I can help it.
  3. I would prefer not to new localname

What prototype and data member combination should I use?

CLARIFICATION: Several answers suggested using the static keyword to ensure that the memory is not automatically de-allocated when unleashMonkeyFish() ends. Since the ultimate goal of this application is to unleash N MonkeyFish (all of which must have unique names) this is not a viable option. (And yes, MonkeyFish - being fickle creatures - often change their names, sometime several times in a single day.)

EDIT: Greg Hewgil has pointed out that it is illegal to store the name variable as a reference, since it is not being set in the constructor. I'm leaving the mistake in the question as-is, since I think my mistake (and Greg's correction) might be useful to someone seeing this problem for the first time.

like image 324
Runcible Avatar asked Feb 26 '09 04:02

Runcible


People also ask

When should you pass by reference?

Use pass-by-reference if you want to modify the argument value in the calling function. Otherwise, use pass-by-value to pass arguments. The difference between pass-by-reference and pass-by-pointer is that pointers can be NULL or reassigned whereas references cannot.

What would be the concern when passing by reference is applied?

Pass by reference (also called pass by address) means to pass the reference of an argument in the calling function to the corresponding formal parameter of the called function so that a copy of the address of the actual parameter is made in memory, i.e. the caller and the callee use the same variable for the parameter.

Should you pass by reference?

Passing Large-Sized ArgumentsIf an argument is significant in size (like a string that's a list), it makes more sense to use pass by reference to avoid having to move the entire string. Effectively, pass by reference will pass just the address of the argument and not the argument itself.

When should you pass by reference C++?

CPP. 2) For passing large sized arguments: If an argument is large, passing by reference (or pointer) is more efficient because only an address is really passed, not the entire object.


2 Answers

One way to do this is to have your string

std::string name;

As the data-member of your object. And then, in the unleashMonkeyFish function create a string like you did, and pass it by reference like you showed

void setName( const std::string & parameter_name ) {
    name = parameter_name;
}

It will do what you want - creating one copy to copy the string into your data-member. It's not like it has to re-allocate a new buffer internally if you assign another string. Probably, assigning a new string just copies a few bytes. std::string has the capability to reserve bytes. So you can call "name.reserve(25);" in your constructor and it will likely not reallocate if you assign something smaller. (i have done tests, and it looks like GCC always reallocates if you assign from another std::string, but not if you assign from a c-string. They say they have a copy-on-write string, which would explain that behavior).

The string you create in the unleashMonkeyFish function will automatically release its allocated resources. That's the key feature of those objects - they manage their own stuff. Classes have a destructor that they use to free allocated resources once objects die, std::string has too. In my opinion, you should not worry about having that std::string local in the function. It will not do anything noticeable to your performance anyway most likely. Some std::string implementations (msvc++ afaik) have a small-buffer optimization: For up to some small limit, they keep characters in an embedded buffer instead of allocating from the heap.

Edit:

As it turns out, there is a better way to do this for classes that have an efficient swap implementation (constant time):

void setName(std::string parameter_name) {
    name.swap(parameter_name);
}

The reason that this is better, is that now the caller knows that the argument is being copied. Return value optimization and similar optimizations can now be applied easily by the compiler. Consider this case, for example

obj.setName("Mr. " + things.getName());

If you had the setName take a reference, then the temporary created in the argument would be bound to that reference, and within setName it would be copied, and after it returns, the temporary would be destroyed - which was a throw-away product anyway. This is only suboptimal, because the temporary itself could have been used, instead of its copy. Having the parameter not a reference will make the caller see that the argument is being copied anyway, and make the optimizer's job much more easy - because it wouldn't have to inline the call to see that the argument is copied anyway.

For further explanation, read the excellent article BoostCon09/Rvalue-References

like image 184
Johannes Schaub - litb Avatar answered Nov 13 '22 19:11

Johannes Schaub - litb


If you use the following method declaration:

void setName( const std::string & parameter_name );

then you would also use the member declaration:

std::string name;

and the assignment in the setName body:

name = parameter_name;

You cannot declare the name member as a reference because you must initialise a reference member in the object constructor (which means you couldn't set it in setName).

Finally, your std::string implementation probably uses reference counted strings anyway, so no copy of the actual string data is being made in the assignment. If you're that concerned about performance, you had better be intimately familiar with the STL implementation you are using.

like image 21
Greg Hewgill Avatar answered Nov 13 '22 19:11

Greg Hewgill