11.6 SELF-REFERENCE IN C


11.6 SELF-REFERENCE IN C++

Sometimes it makes for more convenient code if a member function can access the object on which the function is invoked. Additionally, in some situations a constructor needs to directly access the object under construction. A member function accessing the object on which the function is invoked, or a constructor accessing the object under construction, is referred to as self-reference. Self-reference in both C++ and Java is achieved by using the keyword this.

As an example of self-reference in C++, let's say that for reasons of convenience we want to use the same identifier for a data member of a class as for a parameter in the definition of a constructor for the class. How can we distinguish between the two uses of the same identifier in the implementation code for the constructor? By using the keyword this to qualify the data member, as in the example below:

 
//SelfRef.cc #include <iostream> using namespace std; class X { public: int m; //(A) X( int m ) { this->m = m; } //(B) }; int main() { X xobj( 20 ); cout << "m: " << xobj.m << endl; // m: 20 return 0; }

The name of the data member in line (A) is the same as the identifier used as a parameter in line (B). But this does not create a problem for the compiler since the syntax’ this->m’ makes clear as to which m is what in the body of the constructor in line (B). In general, the keyword this is a pointer to the object on which the method is invoked. For the case of a constructor, the keyword this points to the object under construction.

Self-reference also plays an important role in the definition of those functions whose multiple invocations we want to "chain together", as in the following call:

     some_object.funct(arg_1).funct(arg_2).funct(arg_3); 

This statement calls for invoking funct on some_object. On the object returned by the operation some_object.funct(arg_1), we invoke funct again but with a different argument, and so on. Presumably, funct changes the state of some_object in some way. And presumably we want some_object.funct(arg_1) to return this modified object so that funct (arg_2) can be invoked on it. For funct to be able to return the modified some_object, it would need to access the object on which it is invoked.

The following example illustrates how multiple invocations of a function can be chained together if the function returns the object on which it is invoked. The program shown uses a class SpecialInt, which you may think of as a range-limited integer type for some application. The integer value held by a SpecialInt is not allowed to be outside the range (-100,100). This class comes with a plus function defined as:

     SpecialInt& SpecialInt::plus(SpecialInt sm) {         accumulator += sm.getI();         if ( accumulator > 100 || accmulator < -100 ) throw Err();         return *this;     } 

Note especially the return statement:

     return *this; 

So when the function plus is invoked on a SpecialInt object, it modifies the accumulator in the object on which the function is invoked and returns a reference to this modified object. This allows the chaining together of the plus invocations on the same object. So if we say

     SpecialInt s1( 4 );     SpecialInt s2( 5 );     s1.plus( s2 ); 

the value of the accumulator in the s1 object will become 9. And if we say

     SpecialInt s1( 4 );     SpecialInt s2( 5 );     SpecialInt s3( 6 );     s1.plus( s2 ).plus( s3 ); 

the value of the accumulator in the s1 object will now become 15. In this manner we could chain together as many invocations of plus as we wanted. Here is the complete code:

 
//SpecialInt.cc #include <iostream> using namespace std; class Err {}; class SpecialInt { int i; public: int accumulator; SpecialInt (int m) { if (m > 100 || m < -100) throw Err(); i = m; accumulator = m; } int getI() { return i; } SpecialInt& plus(SpecialInt m); }; SpecialInt& SpecialInt: :plus (SpecialInt sm) { //(A) accumulator += sm.getI(); if (accumulator > 100 || accumulator < -100) throw Err(); return *this; } int main() { SpecialInt s1( 4 ); SpecialInt s2( 5 ); SpecialInt s3( 6 ); SpecialInt s4( 7 ); s1.plus( s2 ).plus( s3 ).plus( s4 ); cout << s1.accumulator << endl; // prints out 22 // SpecialInt s5(101); // range violation return 0; }

It is important to note that the following version of plus will not work, even though the only difference from the version shown previously in line (A) is in the return type. In the following version, we return a copy of the object, as opposed to a reference to the object on which plus is invoked.

     SpecialInt SpecialInt::plus (SpecialInt sm) {                //(B)         accumulator += sm.getI();         if ( accumulator > 100 || accmulator < -100 )             throw Err();         return *this;     } 

With this new version, the object returned by

     SpecialInt s1( 4 );     SpecialInt s2( 5 );     s1.plus( s2 );                                               //(C) 

s1. plus (s2) will actually be a copy of the s1 object constructed previously. While the statement in line (C) above will cause the accumulator data member of s1 to change to 9, a second invocation of plus, as in the statement in line (D) below

     SpecialInt s1( 4 );     SpecialInt s2( 5 );     SpecialInt s3( 6 );     s1.plus( s2 ).plus( s3 );                                    //(D) 

will now be invoked on the copy of s1 returned by s1.plus (s2), as opposed to on the original s1 itself. It is for this reason that, if we use the version of plus shown in line (B), the final value printed out in main above will be the erroneous number 9 as opposed to the correct answer 22.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net