Validating Data with set Functions
In Section 3.6, we introduced set functions for allowing clients of a class to modify the value of a private data member. In Fig. 3.5, class GradeBook defines member function setCourseName to simply assign a value received in its parameter name to data member courseName. This member function does not ensure that the course name adheres to any particular format or follows any other rules regarding what a "valid" course name looks like. As we stated earlier, suppose that a university can print student transcripts containing course names of only 25 characters or less. If the university uses a system containing GradeBook objects to generate the transcripts, we might want class GradeBook to ensure that its data member courseName never contains more than 25 characters. The program of Figs. 3.153.17 enhances class GradeBook's member function setCourseName to perform this validation (also known as validity checking).
GradeBook Class Definition
Notice that GradeBook's class definition (Fig. 3.15)and hence, its interfaceis identical to that of Fig. 3.11. Since the interface remains unchanged, clients of this class need not be changed when the definition of member function setCourseName is modified. This enables clients to take advantage of the improved GradeBook class simply by linking the client code to the updated GradeBook's object code.
Validating the Course Name with GradeBook Member Function setCourseName
The enhancement to class GradeBook is in the definition of setCourseName (Fig. 3.16, lines 1831). The if statement in lines 2021 determines whether parameter name contains a valid course name (i.e., a string of 25 or fewer characters). If the course name is valid, line 21 stores the course name in data member courseName. Note the expression name.length() in line 20. This is a member-function call just like myGradeBook.displayMessage(). The C++ Standard Library's string class defines a member function length that returns the number of characters in a string object. Parameter name is a string object, so the call name.length() returns the number of characters in name. If this value is less than or equal to 25, name is valid and line 21 executes.
Figure 3.15. GradeBook class definition.
(This item is displayed on page 106 in the print version)
1 // Fig. 3.15: GradeBook.h 2 // GradeBook class definition presents the public interface of 3 // the class. Member-function definitions appear in GradeBook.cpp. 4 #include // program uses C++ standard string class 5 using std::string; 6 7 // GradeBook class definition 8 class GradeBook 9 { 10 public: 11 GradeBook( string ); // constructor that initializes a GradeBook object 12 void setCourseName( string ); // function that sets the course name 13 string getCourseName(); // function that gets the course name 14 void displayMessage(); // function that displays a welcome message 15 private: 16 string courseName; // course name for this GradeBook 17 }; // end class GradeBook |
Figure 3.16. Member-function definitions for class GradeBook with a set function that validates the length of data member courseName.
(This item is displayed on pages 106 - 107 in the print version)
1 // Fig. 3.16: GradeBook.cpp 2 // Implementations of the GradeBook member-function definitions. 3 // The setCourseName function performs validation. 4 #include 5 using std::cout; 6 using std::endl; 7 8 #include "GradeBook.h" // include definition of class GradeBook 9 10 // constructor initializes courseName with string supplied as argument 11 GradeBook::GradeBook( string name ) 12 { 13 setCourseName( name ); // validate and store courseName 14 } // end GradeBook constructor 15 16 // function that sets the course name; 17 // ensures that the course name has at most 25 characters 18 void GradeBook::setCourseName( string name ) 19 { 20 if ( name.length() <= 25 ) // if name has 25 or fewer characters 21 courseName = name; // store the course name in the object 22 23 if ( name.length() > 25 ) // if name has more than 25 characters 24 { 25 // set courseName to first 25 characters of parameter name 26 courseName = name.substr( 0, 25 ); // start at 0, length of 25 27 28 cout << "Name "" << name << "" exceeds maximum length (25). " 29 << "Limiting courseName to first 25 characters. " << endl; 30 } // end if 31 } // end function setCourseName 32 33 // function to get the course name 34 string GradeBook::getCourseName() 35 { 36 return courseName; // return object's courseName 37 } // end function getCourseName 38 39 // display a welcome message to the GradeBook user 40 void GradeBook::displayMessage() 41 { 42 // call getCourseName to get the courseName 43 cout << "Welcome to the grade book for " << getCourseName() 44 << "!" << endl; 45 } // end function displayMessage |
The if statement in lines 2330 handles the case in which setCourseName receives an invalid course name (i.e., a name that is more than 25 characters long). Even if parameter name is too long, we still want to leave the GradeBook object in a consistent statethat is, a state in which the object's data member courseName contains a valid value (i.e., a string of 25 characters or less). Thus, we truncate (i.e., shorten) the specified course name and assign the first 25 characters of name to the courseName data member (unfortunately, this could truncate the course name awkwardly). Standard class string provides member function substr (short for "substring") that returns a new string object created by copying part of an existing string object. The call in line 26 (i.e., name.substr( 0, 25 )) passes two integers (0 and 25) to name's member function substr. These arguments indicate the portion of the string name that substr should return. The first argument specifies the starting position in the original string from which characters are copiedthe first character in every string is considered to be at position 0. The second argument specifies the number of characters to copy. Therefore, the call in line 26 returns a 25-character substring of name starting at position 0 (i.e., the first 25 characters in name). For example, if name holds the value "CS101 Introduction to Programming in C++", substr returns "CS101 Introduction to Pro". After the call to substr, line 26 assigns the substring returned by substr to data member courseName. In this way, member function setCourseName ensures that courseName is always assigned a string containing 25 or fewer characters. If the member function has to truncate the course name to make it valid, lines 2829 display a warning message.
Note that the if statement in lines 2330 contains two body statementsone to set the courseName to the first 25 characters of parameter name and one to print an accompanying message to the user. We want both of these statements to execute when name is too long, so we place them in a pair of braces, { }. Recall from Chapter 2 that this creates a block. You will learn more about placing multiple statements in the body of a control statement in Chapter 4.
Note that the cout statement in lines 2829 could also appear without a stream insertion operator at the start of the second line of the statement, as in:
cout << "Name "" << name << "" exceeds maximum length (25). " "Limiting courseName to first 25 characters. " << endl;
The C++ compiler combines adjacent string literals, even if they appear on separate lines of a program. Thus, in the statement above, the C++ compiler would combine the string literals "" exceeds maximum length (25). " and "Limiting courseName to first 25 characters. " into a single string literal that produces output identical to that of lines 2829 in Fig. 3.16. This behavior allows you to print lengthy strings by breaking them across lines in your program without including additional stream insertion operations.
Testing Class GradeBook
Figure 3.17 demonstrates the modified version of class GradeBook (Figs. 3.153.16) featuring validation. Line 14 creates a GradeBook object named gradeBook1. Recall that the GradeBook constructor calls member function setCourseName to initialize data member courseName. In previous versions of the class, the benefit of calling setCourseName in the constructor was not evident. Now, however, the constructor takes advantage of the validation provided by setCourseName. The constructor simply calls setCourseName, rather than duplicating its validation code. When line 14 of Fig. 3.17 passes an initial course name of "CS101 Introduction to Programming in C++" to the GradeBook constructor, the constructor passes this value to setCourseName, where the actual initialization occurs. Because this course name contains more than 25 characters, the body of the second if statement executes, causing courseName to be initialized to the truncated 25-character course name "CS101 Introduction to Pro" (the truncated part is highlighted in red in line 14). Notice that the output in Fig. 3.17 contains the warning message output by lines 2829 of Fig. 3.16 in member function setCourseName. Line 15 creates another GradeBook object called gradeBook2the valid course name passed to the constructor is exactly 25 characters.
Figure 3.17. Creating and manipulating a GradeBook object in which the course name is limited to 25 characters in length.
(This item is displayed on pages 108 - 109 in the print version)
1 // Fig. 3.17: fig03_17.cpp 2 // Create and manipulate a GradeBook object; illustrate validation. 3 #include 4 using std::cout; 5 using std::endl; 6 7 #include "GradeBook.h" // include definition of class GradeBook 8 9 // function main begins program execution 10 int main() 11 { 12 // create two GradeBook objects; 13 // initial course name of gradeBook1 is too long 14 GradeBook gradeBook1( "CS101 Introduction to Programming in C++" ); 15 GradeBook gradeBook2( "CS102 C++ Data Structures" ); 16 17 // display each GradeBook's courseName 18 cout << "gradeBook1's initial course name is: " 19 << gradeBook1.getCourseName() 20 << " gradeBook2's initial course name is: " 21 << gradeBook2.getCourseName() << endl; 22 23 // modify myGradeBook's courseName (with a valid-length string) 24 gradeBook1.setCourseName( "CS101 C++ Programming" ); 25 26 // display each GradeBook's courseName 27 cout << " gradeBook1's course name is: " 28 << gradeBook1.getCourseName() 29 << " gradeBook2's course name is: " 30 << gradeBook2.getCourseName() << endl; 31 return 0; // indicate successful termination 32 } // end main
|
Lines 1821 of Fig. 3.17 display the truncated course name for gradeBook1 (we highlight this in red in the program output) and the course name for gradeBook2. Line 24 calls gradeBook1's setCourseName member function directly, to change the course name in the GradeBook object to a shorter name that does not need to be truncated. Then, lines 2730 output the course names for the GradeBook objects again.
Additional Notes on Set Functions
A public set function such as setCourseName should carefully scrutinize any attempt to modify the value of a data member (e.g., courseName) to ensure that the new value is appropriate for that data item. For example, an attempt to set the day of the month to 37 should be rejected, an attempt to set a person's weight to zero or a negative value should be rejected, an attempt to set a grade on an exam to 185 (when the proper range is zero to 100) should be rejected, etc.
Software Engineering Observation 3.6
Making data members private and controlling access, especially write access, to those data members through public member functions helps ensure data integrity. |
Error-Prevention Tip 3.5
The benefits of data integrity are not automatic simply because data members are made privatethe programmer must provide appropriate validity checking and report the errors. |
Software Engineering Observation 3.7
Member functions that set the values of private data should verify that the intended new values are proper; if they are not, the set functions should place the private data members into an appropriate state. |
A class's set functions can return values to the class's clients indicating that attempts were made to assign invalid data to objects of the class. A client of the class can test the return value of a set function to determine whether the client's attempt to modify the object was successful and to take appropriate action. In Chapter 16, we demonstrate how clients of a class can be notified via the exception-handling mechanism when an attempt is made to modify an object with an inappropriate value. To keep the program of Figs. 3.153.17 simple at this early point in the book, setCourseName in Fig. 3.16 just prints an appropriate message on the screen.