-
November 5th, 2004, 04:03 AM
#1
C++ General: How to deal with references?
Q: What is a reference?
A: A reference is an alias for an object. It holds the address of an object, but behaves syntactically like an object.
Q: How do I initialize a reference?
A: Variables of reference type must be initialized with an object of the type from which the reference type is derived, or with an object of a type that can be converted to the type from which the reference type is derived.
Code:
int intVar = 0;
long longVar = 1;
// ...
long& ref1 = intVar; // error
const long& ref2 = intVar; // OK
long& ref3 = longVar; // OK
The first initialization is not legal. int cannot be converted to long& (C2440), because a reference that is not const cannot be bound to a non-lvalue. The right way is to declare de reference const.
Why does the compiler raises such an error? First we must make the difference between l-values and r-values. In C++ expressions can evaluate to an l-value or an r-value. L-values appear on the left side of an assignment statement. However not every l-value may be used on a left-hand side of an assignment. An l-value can also refer to a constant. An l-value that has not been declared const is often called modifiable l-value. Term r-value is sometimes used to describe an expression that appears on the right side of an assignment statement. All l-values are r-values, but not all r-values are also l-values.
The initializer for a plain Type& must be an l-value (an object whose address you can take) of type Type.
The initializer for a const Type& need not to be an l-value or even of type Type. In such cases the following applies:
- first, implicitly type conversion to Type is applied if necessary,
- then, the resulting value is placed in a temporary variable of type Type,
- finally this temporary variable is used as the value of the initializer.
Taking this example:
Code:
int intVar = 0;
long& ref = intVar;
won’t compile because the initializer for long& must be of type long, and intVal is of type int.
To have this done, we must use a const reference.
Code:
int intVar = 0;
const long& ref = intVar;
The interpretation of this might be:
Code:
int intVar = 0;
long temp = (long)intVar;
const long& ref = temp;
The temporary created to hold a reference initializer persists until the end of its reference's scope.
Once initialized, a reference-type variable always points to the same object; it cannot be modified to point to another object.
Q: Is initialization of a reference an assignment?
A: No, they are not semantically the same, though the syntax can be the same. The initialization specifies the object to which the reference-type variable points; the assignment assigns to the referred-to object through the reference. Continuing with the first example:
Code:
ref2 = 44; // error, ref2 is declared const
ref3 = 11; // OK, longVar and ref3 will be 11
Passing an argument of reference type to a function and returning a value of reference type from a function are initializations, thus the formal arguments to a function are initialized correctly, and so are the references returned.
Q: Is there a situation when I can declare a reference-type variable without an initializer?
A: There are four situations for this:
- function declaration
- function return type declaration
- reference-type class member
Code:
class foo {
int& val;
};
- declaration of extern variable
Q: Can I use a reference to a pointer?
A: References to pointers can be declared in much the same way as references to objects. Declaring a reference to a pointer yields a modifiable value that is used like a normal pointer. The following example illustrates the difference between using a pointer and a reference, and the difference between using a pointer to a pointer and a reference to a pointer.
Code:
#include <iostream>
using namespace std;
struct foo
{
int intVal;
long longVal;
foo(int i, long l):
intVal(i), longVal(l){}
void print()
{
cout << '(' << intVal << ',' << longVal << ')' << endl;
}
};
void alterPtr(foo** ptrFoo) // pointer to a pointer
{
(*ptrFoo)->intVal = 11;
(*ptrFoo)->longVal = 44;
}
void alterRef(foo*& refFoo) // reference to a pointer
{
refFoo->intVal = 22;
refFoo->longVal = 88;
}
int main()
{
foo* pFoo = new foo(1,2); // new pointer object
foo& refFoo = *pFoo; // reference to an object
pFoo->print(); // prints (1,2)
refFoo.print(); // prints (1,2)
alterPtr(&pFoo);
pFoo->print(); // prints (11,44)
refFoo.print(); // prints (11,44)
alterRef(pFoo);
pFoo->print(); // prints (22,88)
refFoo.print(); // prints (22,88)
delete pFoo; // delete the object
return 0;
}
In this example, alterPtr uses double indirection, while alterRef uses reference to a pointer. Basically they do the same.
Q: When should I pass a reference as a function parameter?
A: When you have large objects, it is more efficient to pass a reference, than the object itself. This allows the compiler to pass the address of the object while maintaining the syntax that would have been used to access the object.
Code:
#include <iostream>
using namespace std;
struct foo2
{
int a;
int b;
foo2(int i, int j):
a(i), b(j){}
};
void initFoo(foo2& refFoo)
{
refFoo.a = 0;
refFoo.b = 0;
}
int getSum(const foo2& refFoo)
{
return (refFoo.a + refFoo.b);
}
int main()
{
foo2 _foo(1,2);
cout << "Total: " << getSum(_foo) << endl;
initFoo(_foo);
cout << "Total: " << getSum(_foo) << endl;
return 0;
}
The output is
In this example, both initFoo and getSum are passed a reference to a foo2 object. The difference is that initFoo() is supposed to change the reference object, while getSum() should return the sum of a and b without altering the referenced object. Thus the reference is declared const, which is a guarantee that the function will not change its argument.
Any function prototyped as taking a reference type can accept an object of the same type in its place because there is a standard conversion from typename to typename&.
Q: When should I return a reference from a function?
A: Basically there are two situations:
- The information being returned is a large enough object that returning a reference is more efficient than returning a copy. Just as it can be more efficient to pass large objects to functions by reference, it also can be more efficient to return large objects from functions by reference. Reference-return protocol eliminates the necessity of copying the object to a temporary location prior to returning.
- The type of the function must be an l-value. Reference-return types can also be useful when the function must evaluate to an l-value. Most overloaded operators fall into this category, particularly the assignment operator.
The following example shows the usage of functions declared as returning reference types.
Code:
#include <iostream>
using namespace std;
class Point
{
public:
Point():_x(0), _y(0) {}
Point(int x, int y):_x(x), _y(y) {}
// Define "accessor" functions as reference types.
int& x() {return _x;}
int& y() {return _y;}
private:
int _x;
int _y;
};
int main()
{
Point pt;
// Use x() and y() as r-values.
cout << "x = " << pt.x() << endl << "y = " << pt.y() << endl;
// Use x() and y() as l-values.
pt.x() = 1;
pt.y() = -1;
cout << "x = " << pt.x() << endl << "y = " << pt.y() << endl;
return 0;
}
The output for this program is
Code:
x = 0
y = 0
x = 1
y = -1
x() and y() are functions declared as returning reference types. They can be used on either side of an assignment statement. When such a function is used as l-value the object referenced is altered.
Q: Are there pitfalls to avoid when using references?
A: You must be careful not to go out of scope. That happens for example when you return the address of a local variable or temporary object. The compiler gives you the C4172 warning: returning address of local variable or temporary. You must pay attention to this warning and eliminate it. The following example raises C4172 warning.
Code:
int& getSomething()
{
int something = 33;
return something;
} // something gets out of scope
int main(void)
{
int some = 10;
some = getSomething(); // some is undefined here
return 0;
}
Local variables and temporary objects are destroyed when a function returns, so the address returned is not valid.
Below is another example when the refered item goes out of scope:
Code:
int main()
{
foo* pFoo = new foo(1,2); // new pointer object
foo& refFoo = *pFoo; // reference to an object
pFoo->print(); // prints (1,2)
refFoo.print(); // prints (1,2)
delete pFoo; // delete the object
refFoo.print(); // prints (-17891602,-17891602)
return 0;
}
After deleting pFoo, refFoo is no longer valid and must be used no more.
Please refer to MSDN for more.
Last edited by cilu; February 15th, 2006 at 03:44 PM.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|