Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does "this" cascading work?

Tags:

c++

this

I have the following class interface:

  class Time
  {
  public:
     Time( int = 0, int = 0, int = 0 ); 
     Time &setHour( int );                 
     Time &setMinute( int );               
     Time &setSecond( int ); 

  private:
     int hour; 
     int minute; 
     int second; 
  }; 

The implementation is here:

  Time &Time::setHour( int h ) 
  {
     hour = ( h >= 0 && h < 24 ) ? h : 0; 
     return *this; 
  } 


  Time &Time::setMinute( int m ) 
  {
     minute = ( m >= 0 && m < 60 ) ? m : 0; 
     return *this; 
  } 


  Time &Time::setSecond( int s ) 
  {
     second = ( s >= 0 && s < 60 ) ? s : 0; 
    return *this; 
   }

In my main .cpp file, I have this code:

int main()
{
    Time t;     
    t.setHour( 18 ).setMinute( 30 ).setSecond( 22 );
    return 0;
}

How is it possible to chain these function calls together? I don't understand why this works.

like image 854
teacher Avatar asked Aug 30 '11 21:08

teacher


4 Answers

The reason that this works correctly is that when you call

t.setHour( 18 )

The return value is a Time&, a reference to a Time object. More importantly, it's defined as

Time &Time::setHour( int h ) 
{
   hour = ( h >= 0 && h < 24 ) ? h : 0; 
   return *this;  // <--- right here
}

Inside of a member function, this is a pointer to the object on which the call was made, and *this is a reference to the object on which the call was made (the receiver object). This means that when you call setHour, the function sets the hour on the time, then returns a reference to the Time object on which you made the call. Thus t.setHour( 18 ) both sets the hour and then returns a reference to the receiver object. That way, you can write

t.setHour( 18 ).setMinute( 30 ).setSecond( 22 );

because it's interpreted as

((t.setHour( 18 )).setMinute( 30 )).setSecond( 22 );

and in each case the function returns a reference to t.

More generally, any time a function returns a reference and that reference is *this, any operation you perform on the return value of the function is indistinguishable from operations that you would perform on the object itself.

Hope this helps!

like image 78
templatetypedef Avatar answered Oct 09 '22 09:10

templatetypedef


Each of t's methods return a reference to t. A reference is an alias. So if you did

 Time t;
 Time& tAgain = t;
 tAgain.setMinute( 30 ); 

tAgain.setMinute also alters t's time.

Now extrapolate that simple example to cascading. Each method of t returns a reference to itself

  Time &Time::setSecond( int s ) 
  {
      second = ( s >= 0 && s < 60 ) ? s : 0; 
      return *this; 
  }

So in the expression:

  t.setHour( 18 ).setMinute( 30 )

t.setHour( 18 ) calls setHour on t, then returns a reference to t. In this case the reference is temporary. So you can think of it as if the above line changed to the following on evaluating setHour:

  tAgain.setMinute(30);

t.setHour returned a reference -- similar to our tAgain above. Just an alias for t itself.

like image 35
Doug T. Avatar answered Oct 09 '22 07:10

Doug T.


Due to the fact that each function returns a reference to the this object object (The return *this).

Basically this means that every time the function is called it make the relevant changes and then passes the entire object back out as a reference. It is then possible to make calls on that returned object.

It could also be written as follows:

 Time t;
 Time& t1 = t.setHour( 18 ); // t1 will refer to the same object as t.
 Time& t2 = t1.setMinute( 30 ); // t2 will refer to the same object as t1 and t.
 Time& t3 = t2.setSecond( 22 ); // t3 will refer to the same object as t2, t1 and t.

That may make it easier to understand what is going on.

like image 42
Goz Avatar answered Oct 09 '22 07:10

Goz


This is similar to overloading stream operators.

ostream& operator<<(ostream& s, const T& val)
{
    s << val;
    return s;
}

You do this because you modify the stream and return it so it can be used in the next cascading call if desired. It keeps getting passed by reference so it can keep going into the next expression segment.

Thats how:

std::cerr << 1 << 2 << 3 << std::endl;

works! :)

like image 42
John Humphreys Avatar answered Oct 09 '22 08:10

John Humphreys