9.9 Time Class Case Study: A Subtle Trap — Returning a Reference or a Pointer 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 statement. In this context, the reference makes a perfectly acceptable lvalue that can receive a value.

A member function can return a reference to a private data member of that class. If the reference return type is declared const, the reference is a nonmodifiable lvalue and cannot be used to modify the data. However, if the reference return type is not declared const, subtle errors can occur.

The program of Figs. 9.119.13 uses a simplified Time class (Fig. 9.11 and Fig. 9.12) to demonstrate returning a reference to a private data member with member function badSetHour (declared in Fig. 9.11 in line 13 and defined in Fig. 9.12 in lines 25–34). 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! A similar problem would occur if a pointer to the private data were to be returned by the function.

Fig. 9.11 Time class declaration.

Alternate View

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

Fig. 9.12 Time class member-function definitions.

Alternate View

 1   // Fig. 9.12: Time.cpp
 2   // Time class member-function definitions.
 3   #include <stdexcept>
 4   #include "Time.h" // include definition of class Time
 5   using namespace std;
 6
 7   // set values of hour, minute and second
 8   void Time::setTime(int h, int m, int s) {
 9      // validate hour, minute and second
10      if ((h >= 0 && h < 24) && (m >= 0 && m < 60) && (s >= 0 && s < 60)) {
11         hour = h;
12         minute = m;
13         second = s;
14      }
15      else {
16         throw invalid_argument(
17            "hour, minute and/or second was out of range");
18      }
19    }
20
21    // return hour value
22    unsigned int Time::getHour() const {return hour;}
23
24    // poor practice: returning a reference to a private data member.
25    unsigned int& Time::badSetHour(int hh) {                         
26       if (hh >= 0 && hh < 24) {
27          hour = hh;
28       }
29       else {
30          throw invalid_argument("hour must be 0-23");
31       }
32
33       return hour; // dangerous reference return
34    }

Figure 9.13 declares Time object t (line 9) and reference hourRef (line 12), which is initialized with the reference returned by the call t.badSetHour(20). Line 14 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 in an object of the class. Next, line 15 uses the alias to set the value of hour to 30 (an invalid value) and line 16 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 20 uses the badSetHour function call itself as an lvalue and assigns 74 (another invalid value) to the reference returned by the function. Line 25 again displays the value returned by function getHour to show that assigning a value to the result of the function call in line 20 modifies the private data in the Time object t.

Software Engineering Observation 9.7

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. There are cases where doing this is appropriate—we’ll show an example of this when we build our custom Array class in Section 10.10.

Fig. 9.13 public member function that returns a reference to a private data member.

Alternate View

 1   // Fig. 9.13: fig09_13.cpp
 2   // Demonstrating a public member function that
 3   // returns a reference to a private data member.
 4   #include <iostream>
 5   #include "Time.h" // include definition of class Time
 6   using namespace std;
 7
 8   int main() {
 9      Time t; // create Time object
10
11      // initialize hourRef with the reference returned by badSetHour
12      unsigned int& hourRef{t.badSetHour(20)}; // 20 is a valid hour 
13
14      cout << "Valid hour before modification: " << hourRef;            
15      hourRef = 30; // use hourRef to set invalid value in Time object t
16      cout << "
Invalid hour after modification: " << t.getHour();
17
18      // Dangerous: Function call that returns                        
19      // a reference can be used as an lvalue!                        
20      t.badSetHour(12) = 74; // assign another invalid value to hour  
21
22      cout << "

*************************************************
"
23         << "POOR PROGRAMMING PRACTICE!!!!!!!!
"
24         << "t.badSetHour(12) as an lvalue, invalid hour: "
25         << t.getHour()
26         << "
*************************************************" << endl;
27   }

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

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

You can't read the all page of ebook, please click here login for view all page.
Reset