Passing as const and by reference - Worth it?

C++

C++ Problem Overview


> Possible Duplicate:
> How to pass objects to functions in C++?

In my game I make excessive use of mathematical vectors and operator overloading of them.

class Vector
{
   float x, y;
};

That's basically all about my Vector class (methods excluded).

I am not an expert in C++ and I've seen and read about passing as const and passing by reference.

So, where are the performance differences in the below code example?

Float RandomCalculation( Vector a, Vector b )
{
	return a.x * b.x / b.x - a.x * RANDOM_CONSTANT;
}

// versus..

Float RandomCalculation( Vector& a, Vector& b )
{
	return a.x * b.x / b.x - a.x * RANDOM_CONSTANT;
}

// versus..

Float RandomCalculation( const Vector& a, const Vector& b )
{
	return a.x * b.x / b.x - a.x * RANDOM_CONSTANT;
}
  • Which of the three should I use, and why?

  • What advantages for the optimization process of the compiler does each of the options have?

  • When and where do I have to be especially careful?

C++ Solutions


Solution 1 - C++

Passing by const reference is the preferred way to pass around objects as a smart alternative to pass-by-value. When you pass by const reference, you take the argument in by reference (avoiding making any copies of it), but cannot make any changes to the original object (much as would happen when you would take the parameters in by value).

If you consider these three functions:

void VersionOne(Vector v);
void VersionTwo(Vector& v);
void VersionThree(const Vector& v);

There are subtle distinctions between all of them. The first function, for example, will invoke the copy constructor when you pass in a Vector so that it has its own local copy of the Vector. If your copy constructor takes a while to run or does a lot of resource allocation and deallocation, this may be slow, but you can make any changes you want to the parameter without risking any changes propagating back up to the caller. There will also be a destructor call at the end of the function as the argument is cleaned up, and if this is too large a cost it may be advisable to avoid this setup. That said, for small objects it may be perfectly acceptable.

The second version of this function takes in a Vector by reference, which means that the function can make any changes it wants to the Vector and the changes will propagate back up to the caller. Whenever you see a function that takes an argument by non-const reference, like this VersionTwo function, you should assume that it will be modifying the argument, since if it weren't going to make any modifications, it would be taken by const reference. You will most likely take in the value by reference if you need to make changes to the Vector; for example, by rotating it, scaling it, etc. One tradeoff involved here is that the Vector will not be copied when it is passed into this function, and so you will avoid a call to the copy constructor and destructor. This may have performance implications for your program, though if that's your reasoning you should probably go with pass by const reference. One thing to note is that following a reference is very similar to following a pointer (in fact, most implementations of references just treat them as though they were automatically-dereferenced pointers), so there may be a small performance hit every time you access the data through the reference. Only profiling can tell you whether or not this is a major problem, though, and I wouldn't worry about it unless you had a specific reason to think it was at fault.

The final version of this function takes in a Vector by const reference, which, like passing by regular reference, avoids any copying. However, when taking the Vector by const reference, you are prohibited from making any changes to the Vector inside the function, so clients can assume that the Vector will not be modified. (Yes, technically it could be modified if it is poorly-written or has mutable data members, but we'll ignore that for now. It's the high-level idea that's important here). This option would be good if you wanted to be able to inspect the value in the function without copying it and without mutating it.

There is one more difference between pass-by-reference and pass-by-const-reference, and that's the behavior of the function on rvalues. If you have a temporary Vector object - either you created it explicitly by writing Vector() or by doing some mathematical operation on it like writing v1 + v2 - then you cannot pass that temporary Vector into a function that takes its parameter by reference because references can only bind to lvalues. The idea is that if you have a function like this:

void DoSomething(Vector& v) {
     v.x = 0.0f;
}

Then it doesn't make sense to write

DoSomething(v1 + v2);

Since this would be changing the x field of a temporary expression. To prevent this, the compiler will refuse to compile this code.

However, C++ makes an exception and lets you pass rvalues into functions that take their argument by const reference, because, intuitively, you shouldn't be able to modify an object through a const reference. Thus this code is perfectly legal:

void DoSomething(const Vector& v) {
    cout << v.x << endl;
}

DoSomething(v1 + v2);

So, to summarize-

  1. Pass-by-value and pass-by-const-reference imply similar things - you want to be able to look at the value without being able to modify it.
  2. Any time you could use pass-by-value you could instead use pass-by-const-reference without affecting the correctness of the program. However, there are performance tradeoffs between the indirection of the reference and the cost of copying and destructing the parameter.
  3. Pass-by-non-const-reference should be used to indicate "I want to modify the argument."
  4. You cannot pass rvalues into functions that take their arguments by non-const reference.

Hope this helps!

Solution 2 - C++

Do you want to copy the objects passed to the function? If you pass the objects "Vector a" and "Vector b" by value, you have to deal with the overhead of copying them. For small structures the overhead is negligible.

If you pass the objects by reference or by pointer, you don't have this overhead. However, passing by pointer or reference potentially allows for modification of the object:

The const keyword in front of the object name is used to guarantee that your function does not modify the objects that are passed to the function by reference or pointer. Not only will this tell other programmers that your function is safe to use, it is also strictly enforced by the compiler. The const keyword does not have an impact on performance when using it for this purpose.

If you have large objects, pass them by const reference or const pointer. If you want to modify the object that is passed to the function, use a reference or a pointer. If your object is a small structure you can pass it by value.

Solution 3 - C++

You'll get a lot of answers to this question claiming one thing or another. The truth of the matter is that you need to test it. Your object is fairly small so that passing by value may be as fast or faster than passing by reference. Only by profiling your code will you be able to know.

Solution 4 - C++

You only pass by reference to non-const if you want to change the arguments and have the client observe those changes.

If you don't want to change the arguments, pass by reference to const or by value.

If you want to change the arguments but have no effect on the client, pass by value.

Solution 5 - C++

May be now things are changed but I did some tests many years ago with several C++ compilers and different variations (e.g. member or non-member operators, operator+ defined in terms of operator+= or not, pass by value or by ref and the like).

I checked both timings and the generated assembler code. The results where highly dependent on which compiler was used... the best for one was not the best for another.

Also, sadly enough, at that time I was never able to get the same performance of manually unrolled C code performing the same computations. Close yes... but C was still somewhat faster.

Solution 6 - C++

The short answer is that there will be little difference in practice.

Now for the long answer:

There will be a performance difference between the first and second versions, and there may be a difference between the second and third.

When you call the first version (Vector a, Vector b) a copy of each of the arguments will be made on the stack. This involves allocating stack memory and copying the member fields of the Vector class.

The second version will not copy the Vector objects. Instead, memory will be allocated for the two references (probably 4 or 8 bytes each depending on your machine) and populated with the address of the caller's Vector objects. This is a little less memory allocation and a couple fewer writes.

The third version will probably not be any more performant. The const keyword is useful for ensuring that your code executes without unexpected side effects so it's probably good practice to use it, but is unlikely to result in faster code. The compiler can use const as a hint that allows some optimisations but it might perform those optimisations anyway.

In your case, the Vector class is so small that there is unlikely to be any practical difference unless you are making a huge number of calls. It's much more important to understand the different semantics between your first and second versions. In the first version, changes to a and b do not impact the caller's view of those objects. In the second version (with references) changes to a and b do affect the caller's view.

Long story short: Get the semantics right first, then worry about optimisation. Be wary of premature optimisation. If you really want to optimise this kind of thing then get a good book on the internals of C++ and get a deep understanding of exactly what the compiler does when it encounters pass-by-value and pass-by-reference functions.

In your case I'd suggest using version 3 because the 'const' shows your intent and passing by reference removes an unnecessary copy.

EDIT: templatetypedef said it better.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionYukonView Question on Stackoverflow
Solution 1 - C++templatetypedefView Answer on Stackoverflow
Solution 2 - C++MaartenView Answer on Stackoverflow
Solution 3 - C++Edward StrangeView Answer on Stackoverflow
Solution 4 - C++fredoverflowView Answer on Stackoverflow
Solution 5 - C++6502View Answer on Stackoverflow
Solution 6 - C++Cameron SkinnerView Answer on Stackoverflow