开发者

Why is it preferable to write func( const Class &value )?

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 pr开发者_开发知识库oduce 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.


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.


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.


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;
}


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).


Here are the differences between some parameter declarations:

                           copied  out  modifiable
func(Class value)           Y       N    Y
func(const Class value)     Y       N    N
func(Class &value)          N       Y    Y
func(const Class &value)    N       N    N

where:

  • copied: a copy of the input parameter is made when the function is called
  • out: value is an "out" parameter, which means modifications made within func() will be visible outside the function after it returns
  • modifiable: value can be modified within func()

So the differences between func(Class value) and func(const Class &value) are:

  • The first one makes a copy of the input parameter (by calling the Class copy constructor), and allows code inside func() to modify value
  • The second one does not make a copy, and does not allow code inside func() to modify value


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.


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".


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜