lvalue References (C++)

From RAD Studio
Jump to: navigation, search

Go Up to C++ Specifics Index


In the C programming language, you can pass arguments only by value. In C++, you can pass arguments by value or by reference. C++ reference types, closely related to pointer types, create aliases for objects.

Note: Pointer referencing and dereferencing specific for C++ are discussed in Reference/Dereference Operators.

Operator & is used to create lvalue references:

int  i = 0;
int &ir = i; /* ir is an alias for i */
ir = 2; /* same effect as i = 2 */

This creates the lvalue ir as an alias for i, provided the initializer is the same type as the reference. Any operations on ir have precisely the same effect as operations on i. For example, ir = 2 assigns 2 to i, and &ir returns the address of i.

Reference arguments

The reference declarator can also be used to declare reference type parameters within a function:

void func1 (int i);
void func2 (int &ir);   // ir is type "reference to int"

// ...
int sum = 3;
func1(sum);             // sum passed by value
func2(sum);             // sum passed by reference

The sum argument passed by reference can be changed directly by func2. On the other hand, func1 gets a copy of the sum argument (passed by value), so sum itself cannot be altered by func1.

When an actual argument x is passed by value, the matching formal argument in the function receives a copy of x. Any changes to this copy within the function body are not reflected in the value of x outside the scope of the function. Of course, the function can return a value that could be used later to change x, but the function cannot directly alter a parameter passed by value.

In C, changing the value of a function parameter outside the scope of the function requires that you pass the address of the parameter. The address is passed by value, thus changing the contents of the address affects the value of the parameter outside the scope of the function.

Even if the function does not need to change the value of a parameter, it is still useful to pass the address (or a reference) to a function. This is especially true if the parameter is a large data structure or object. Passing an object directly to a function necessitates copying the entire object.

Compare the three implementations of the function treble:

#1 #2 #3
int treble_1(int n)
{
    return 3 * n;
}

// ...
int x, i = 4;
x = treble_1(i);
/* x now = 12, i = 4 */
void treble_2(int* np)
{
    *np = (*np) * 3;
}

// ...
treble_2(&i);
/* i now = 2 */
void treble_3(int &n/* n is a reference type */)
{
    n = n * 3;
}

// ...
treble_3(i);
/* i now = 36 */

The formal argument declaration type& t establishes t as type "reference to type." So, when treble_3 is called with the real argument i, i is used to initialize the formal reference argument n. n therefore acts as an alias for i, so n = n*3 also assigns 3 * i to i.

If the initializer is a constant or an object of a different type than the reference type, a temporary object for which the reference acts as an alias is created:

int& ir = 6; /* temporary int object created, aliased by ir, gets value 6 */
float f;
int& ir2 = f; /* creates temporary int object aliased by ir2;
                      f converted before assignment */
ir2 = 2.0; /* ir2 now = 2, but f is unchanged *

The automatic creation of temporary objects permits the conversion of reference types when formal and actual arguments have different (but assignment-compatible) types. When passing by value, of course, there are fewer conversion problems, since the copy of the actual argument can be physically changed before assignment to the formal argument.

See also