CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 1 of 1
  1. #1
    Join Date
    Oct 2002
    Location
    Timisoara, Romania
    Posts
    14,360

    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:
    1. first, implicitly type conversion to Type is applied if necessary,
    2. then, the resulting value is placed in a temporary variable of type Type,
    3. 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
      Code:
      void foo(int&);
    • function return type declaration
      Code:
      int& foo();
    • reference-type class member
      Code:
      class foo {
         int& val;
      };
    • declaration of extern variable
      Code:
      extern int& val;



    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
    Code:
    Total: 3
    Total: 0
    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
  •  





Click Here to Expand Forum to Full Width

Featured