Passing By Reference-to-const
By Dan Saks
Embedded Systems Design
(10/01/01, 09:13:58 H EDT)
The rules for initializing references make passing by reference-to-const an efficient and attractive alternative to passing by value.
As in C, function calls in C++ normally pass arguments by value. For example, given:
int abs(int i);
calling abs(n) passes argument n to function abs by value. That is, the program copies the value of n to abs's parameter i each time it makes this call.
C++ uses pass-by-value even for objects of class types. (In C++, class types include struct and union types.) For example, given some struct type:
// a whole bunch of fields
int test(gadget g);
calling test(x) passes argument x by value. Much as before, the program copies x's value to parameter g each time it makes this call.
For large struct objects, passing by value can be very slow because it copies each byte of the argument to the corresponding byte of the parameter, as if by calling the Standard C memcpy function. The program does not necessarily call memcpy to do the job, but the argument passing mechanism behaves as if it did. Strictly speaking, passing a class object by value in C++ conceptually calls a special function associated with the class called the copy constructor. However, when the class is just a struct as in C, the copy constructor behaves essentially like memcpy.
You can avoid the cost of copying a large argument by passing it by address rather than by value. You simply change the function declaration to:
int test(gadget *g);
and then change the function call to test(&x). Passing &x (the address of x) is often a lot less work than passing an entire copy of x.
Unfortunately, when you change the parameter type from gadget to "pointer to gadget," you introduce a change in the function's behavior, or at least the possibility of a change. When passing by value, the function only sees a copy of the original gadget, so it cannot change that gadget. In my example, when passing by value, calling test(x) cannot change x. When passing by address, the function can dereference its pointer parameter and store new values into the gadget argument. That is, calling test(&x) might change x. Of course, you can prevent the function from tampering with its argument by using the const qualifier, as in:
通过引用传递值时，可以放弃指针指向的参数而存入新的参数，也就是说 test(&x)可以改变x的值。当然，你可以通过添加const 修饰符来避免参数被修改。
int test(gadget const *g); //注意const的位置，这和const gadget是否一样呢？
For very large objects, passing by address (with const) is almost always faster than passing by value. For objects of modest size (on the order of eight to 16 bytes), it isn't always clear at the outset whether passing by address will actually be faster than passing by value. It depends on the target machine's architecture and what the function does with the parameter. Sometimes you just have to make your best guess and wait to measure the performance of the running program. If it turns out that you guessed wrong, you may have to rewrite the function.
Unfortunately, rewriting a function call so that it uses pass-by-address instead of pass-by-value, or vice versa, can be a bona fide maintenance headache. Changing the function declaration from:
int test(gadget g);
int test(gadget const *g);
isn't much fuss, nor is changing the corresponding function definition. But numerous function calls might be scattered throughout the code, and you have to rewrite all those calls. In some cases, a call such as text(x) becomes test(&x). In others, test(*p) becomes test(p) (where p is a "pointer to gadget").
In C++, passing by reference offers an alternative to passing by address. You can declare the test function as:
int test(gadget const &g);
In this case, parameter g has type "reference to const gadget." This lets you write the call as test(x), as if it were passing by value, but it yields the exact same performance as if it were passing by address. Again, the const qualifier prevents the function from changing the value of its actual gadget argument.
Changing a function parameter from passing by value to passing by reference-to-const is a fairly minor change. You must change the function's definition and corresponding declaration, if any, appearing in a header. Although you must recompile any code that calls the function, you need not rewrite the calls.
In short, passing by reference-to-const is a potentially efficient alternative to passing by value. To the calling function, an argument passed by reference-to-const looks and acts just like one passed by value. The argument expression in the call looks the same either way-you need not add or remove *s or &s when switching from one form to the other.
When passing by value, the called function can't store into the actual argument because it only has access to a copy. When passing by reference-to-const, the called function can't store into the actual argument because it's const.
Dan Saks is the president of Saks & Associates, a C/C++ training and consulting company. He is also a consulting editor for the C/C++ Users Journal. You can write to him at email@example.com.