9.9. Time Class Case Study: A Subtle Trap—Returning a Reference to a private Data Member

A reference to an object is an alias for the name of the object and, hence, may be used on the left side of an assignment. In this context, the reference is an lvalue that can receive a value. One way to use this capability (unfortunately!) is to have a public member function of a class return a reference to a private data member of that class. Note that if a function returns a const reference, that reference cannot be used as a modifiable lvalue.

The program of Figs. 9.149.16 uses a simplified Time class (Fig. 9.14 and Fig. 9.15) to demonstrate returning a reference to a private data member with member function badSetHour (declared in Fig. 9.14 in line 15 and defined in Fig. 9.15 in lines 29–33). Such a reference return actually makes a call to member function badSetHour an alias for private data member hour! The function call can be used in any way that the private data member can be used, including as an lvalue in an assignment statement, thus enabling clients of the class to clobber the class's private data at will! Note that the same problem would occur if a pointer to the private data were to be returned by the function.

Fig. 9.14. Time class declaration.

 

 1   // Fig. 9.14: Time.h
 2   // Time class declaration.
 3   // Member functions defined in Time.cpp
 4
 5   // prevent multiple inclusions of header file
 6   #ifndef TIME_H
 7   #define TIME_H
 8
 9   class Time
10   {
11   public:
12      Time( int = 0, int = 0, int = 0 );
13      void setTime( int, int, int );
14      int getHour();
15      int &badSetHour( int ); // DANGEROUS reference return
16   private:
17      int hour;
18      int minute;
19      int second;
20   }; // end class Time
21
22   #endif

					  


Fig. 9.15. Time class member-function definitions.

 

 1   // Fig. 9.15: Time.cpp
 2   // Time class member-function definitions.
 3   #include "Time.h" // include definition of class Time
 4
 5   // constructor function to initialize private data;
 6   // calls member function setTime to set variables;
 7   // default values are 0 (see class definition)
 8   Time::Time( int hr, int min, int sec )
 9   {
10      setTime( hr, min, sec );
11   } // end Time constructor
12
13   // set values of hour, minute and second
14   void Time::setTime( int h, int m, int s )
15   {
16      hour = ( h >= 0 && h < 24 ) ? h : 0; // validate hour
17      minute = ( m >= 0 && m < 60 ) ? m : 0; // validate minute
18      second = ( s >= 0 && s < 60 ) ? s : 0; // validate second
19   } // end function setTime
20
21   // return hour value
22   int Time::getHour()
23   {
24      return hour;
25   } // end function getHour
26
27   // POOR PROGRAMMING PRACTICE:                     
28   // Returning a reference to a private data member.
29   int &Time::badSetHour( int hh )                   
30   {                                                 
31      hour = ( hh >= 0 && hh < 24 ) ? hh : 0;        
32      return hour; // DANGEROUS reference return     
33   } // end function badSetHour                      

					  


Figure 9.16 declares Time object t (line 12) and reference hourRef (line 15), which is initialized with the reference returned by the call t.badSetHour(20). Line 17 displays the value of the alias hourRef. This shows how hourRef breaks the encapsulation of the class—statements in main should not have access to the private data of the class. Next, line 18 uses the alias to set the value of hour to 30 (an invalid value) and line 19 displays the value returned by function getHour to show that assigning a value to hourRef actually modifies the private data in the Time object t. Finally, line 23 uses the badSetHour function call itself as an lvalue and assigns 74 (another invalid value) to the reference returned by the function. Line 28 again displays the value returned by function getHour to show that assigning a value to the result of the function call in line 23 modifies the private data in the Time object t.

Fig. 9.16. Returning a reference to a private data member.

 

 1   // Fig. 9.16: fig09_16.cpp
 2   // Demonstrating a public member function that
 3   // returns a reference to a private data member.
 4   #include <iostream>
 5   using std::cout;
 6   using std::endl;
 7
 8   #include "Time.h" // include definition of class Time
 9
10   int main()
11   {
12      Time t; // create Time object
13
14      // initialize hourRef with the reference returned by badSetHour
15      int &hourRef = t.badSetHour( 20 ); // 20 is a valid hour       
16
17      cout << "Valid hour before modification: " << hourRef;            
18      hourRef = 30; // use hourRef to set invalid value in Time object t
19      cout << "\nInvalid hour after modification: " << t.getHour();
20
21      // Dangerous: Function call that returns                        
22      // a reference can be used as an lvalue!                        
23      t.badSetHour( 12 ) = 74; // assign another invalid value to hour
24
25      cout << "\n\n*************************************************\n"
26         << "POOR PROGRAMMING PRACTICE!!!!!!!!\n"
27         << "t.badSetHour( 12 ) as an lvalue, invalid hour: "
28         << t.getHour()
29         << "\n*************************************************" << endl;
30      return 0;
31   } // end main

					  

Valid hour before modification: 20
Invalid hour after modification: 30

*************************************************
POOR PROGRAMMING PRACTICE!!!!!!!!
t.badSetHour( 12 ) as an lvalue, invalid hour: 74
*************************************************


Error-Prevention Tip 9.4

Returning a reference or a pointer to a private data member breaks the encapsulation of the class and makes the client code dependent on the representation of the class's data; this is a dangerous practice that should be avoided.