views:

284

answers:

9

Why would one use func( const Class &value ) rather than just func( Class value )? Surely modern compilers will do the most efficient thing using either syntax. Is this still necessary or just a hold over from the days of non-optimizing compilers?

  • Just to add, gcc will produce similar assembler code output for either syntax. Perhaps other compilers do not?

Apparently, this is just not the case. I had the impression from some code long ago that gcc did this, but experimentation proves this wrong. Credit is due to to Michael Burr, whose answer to a similar question would be nominated if given here.

+16  A: 

There are 2 large semantic differences between the 2 signatures.

The first is the use of & in the type name. This signals the value is passed by reference. Removing this causes the object to be passed by value which will essentially pass a copy of the object into the function (via the copy constructor). For operations which simply need to read data (typical for a const &) doing a full copy of the object creates unnecssary overhead. For classes which are not small or are collections, this overhead is not trivial.

The second is the use of const. This prevents the function from accidentally modifying the contents of value via the value reference. It allows the caller some measure of assurance the value will not be mutated by the function. Yes passing a copy gives the caller a much deeper assurance of this in many cases.

JaredPar
Even with a copy, I usually write `func(const Value myVal)` so that I don't accidentally try to modify a temporary object within the function (unless it is passed by value just for this). This is just a small trick to avoid hours of tracking why the object state has not changed after the execution of `func` :)
Matthieu M.
+7  A: 

The first form doesn't create a copy of the object, it just passes a reference (pointer) to the existing copy. The second form creates a copy, which can be expensive. This isn't something that is optimized away: there are semantic differences between having a copy of an object vs. having the original, and copying requires a call to the class's copy constructor.

For very small classes (like <16 bytes) with no copy constructor it is probably more efficient to use the value syntax rather than pass references. This is why you see void foo(double bar) and not void foo(const double &var). But in the interests of not micro-optimizing code that doesn't matter, as a general rule you should pass all real-deal objects by reference and only pass built-in types like int and void * by value.

John Kugelman
+2  A: 

If you use the former, and then try to change value, by accident, the compiler will give you an error.

If you use the latter, and then try to change value, it won't.

Thus the former makes it easier to catch mistakes.

Amber
+1  A: 

The first example is pass by reference. Rather than pass the type, C++ will pass a reference to the object (generally, references are implemented with pointers... So it's likely an object of size 4 bytes)... In the second example, the object is passed by value... if it is a big, complex object then likely it's a fairly heavyweight operation as it involves copy construction of a new "Class".

dicroce
+5  A: 

Surely modern compilers will do the most efficient thing using either syntax

The compiler doesn't compile what you "mean", it compiles what you tell it to. Compilers are only smart for lower level optimizations and problems the programmer overlooks (such as computation inside a for loop, dead code etc).

What you tell the compiler to do in the second example, is to make a copy of the class - which it will do without thinking - even if you didn't use it, that's what you asked the compiler to do.

The second example explicitly asks the compiler to use the same variable - conserving space and precious cycles (no copy is needed). The const is there for mistakes - since Class &value can be written to (sometimes it's desired).

LiraNuna
+1  A: 

The reason that an optimizing compiler can't handle this for you is the issue of separate compilation. In C++, when the compiler is generating code for a caller, it may not have access to the code of the function itself. The most common calling convention that I know of usually has the caller invoke the copy-constructor which means it's not possible for the compilation of the function itself to prevent the copy constructor if it's not necessary.

R Samuel Klatchko
Actually, gcc does do this optimization, even with -O0.
Hmm, I just tested it (using gcc 4.2.1 under Snow Leopard) by putting a printf() into the copy constructor of a class, and then passing an object of that class to a function. Sure enough, the copy constructor was called. In fact, I'm pretty sure the C++ standard requires the copy constructor to be called in that scenario -- to optimize the call out would be a compiler bug.
Jeremy Friesner
@casualcoder - could you post the code you say GCC performs this optimization on? Does it do the same if the caller and callee are not in the same source file?
Michael Burr
+2  A: 
Greg Hewgill
+7  A: 

There is a huge difference which nobody has mentioned yet: object slicing. In some cases, you may need const& (or &) to get correct behavior.

Consider another class Derived which inherits from Class. In client code, you create an instance of Derived which you pass to func(). If you have func(const Class&), that same instance will get passed. As others have said, func(Class) will make a copy, you will have a new (temporary) instance of Class (not Derived) in func.

This difference in behavior (not performance) can be important if func in turn does a downcast. Compare the results of running the following code:

#include <typeinfo.h>

struct Class
{
    virtual void Foo() {};
};
class Derived : public Class {};

void f(const Class& value)
{
    printf("f()\n");

    try
    {
     const Derived& d = dynamic_cast<const Derived&>(value);
     printf("dynamic_cast<>\n");
    }
    catch (std::bad_cast)
    {
     fprintf(stderr, "bad_cast\n");
    }
}

void g(Class value)
{
    printf("g()\n");

    try
    {
     const Derived& d = dynamic_cast<const Derived&>(value);
     printf("dynamic_cast<>\n");
    }
    catch (std::bad_cast)
    {
     fprintf(stderr, "bad_cast\n");
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    Derived d;
    f(d);
    g(d);
    return 0;
}
Dan
+1. And in particular, object slicing means that the compiler *must not* make the optimisation the questioner expects (at least for types that might be polymorphic), because then the object would not be sliced, even though the standard requires it to be.
Steve Jessop
A: 

The only time that passing a parameter by value is preferable is when you are going to copy the parameter anyway.

std::string toUpper( const std::string &value ) {
    std::string retVal(value);
    transform(retVal.begin(), retVal.end(), charToUpper());
    return retVal;
}

Or

std::string toUpper( std::string value ) {
    transform(value.begin(), value.end(), charToUpper());
    return value;
}

In this case the second example is the same speed as the first if the value parameter is a regular object, but faster if the value parameter is a R-Value.

Although most compilers will do this optimisation already I don't expect to rely on this feature till C++0X, esp since I expect it could confuse most programmers who would probably change it back.

See Want Speed? Pass by Value. for a better explaination than I could give.

iain
Should be `return value;` in the second function.
Steve Jessop
you are right changed.
iain