3.10. 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.15–3.17 enhances class
GradeBook's member function setCourseName to perform this
validation.
GradeBook Class Definition
Notice that GradeBook's class definition (Fig. 3.15)—and hence,
its interface—is 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.
Fig. 3.15. GradeBook class
definition.
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 <string> // 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
|
Validating the Course Name with
GradeBook Member Function setCourseName
The enhancement to class GradeBook is in the
definition of setCourseName (Fig. 3.16, lines 18–31). The
if statement in lines 20–21 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.
Fig. 3.16. Member-function definitions for class
GradeBook with a set function that
validates the length of data member courseName.
1 // Fig. 3.16: GradeBook.cpp
2 // Implementations of the GradeBook member-function definitions.
3 // The setCourseName function performs validation.
4 #include <iostream>
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).\n"
29 << "Limiting courseName to first 25 characters.\n" << 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\n" << getCourseName()
44 << "!" << endl;
45 } // end function displayMessage
|
The if statement in lines 23–30 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
state—that 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 copied—the
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 28–29 display a warning message.
Note that the if statement in lines 23–30 contains two body
statements—one 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'll learn more about
placing multiple statements in the body of a control statement in Chapter
4.
Note that the
statement in lines 28–29 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).\n"
"Limiting courseName to first 25 characters.\n" << 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).\n" and "Limiting courseName to
first 25 characters.\n" into a single string literal
that produces output identical to that of lines 28–29 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.15–3.16) featuring
validation. Line 14 creates a GradeBook object named
gradeBook1. Recall that the GradeBook
constructor calls 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
bold black in line 14). Notice that the output in Fig. 3.17 contains the warning message output by lines 28–29
of Fig. 3.16 in
member function setCourseName. Line 15 creates another
GradeBook object called gradeBook2—the
valid course name passed to the constructor is exactly 25 characters.
Fig. 3.17. Creating and manipulating a
GradeBook object in which the course name is
limited to 25 characters in length.
1 // Fig. 3.17: fig03_17.cpp
2 // Create and manipulate a GradeBook object; illustrate validation.
3 #include <iostream>
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 << "\ngradeBook2'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 << "\ngradeBook1's course name is: "
28 << gradeBook1.getCourseName()
29 << "\ngradeBook2's course name is: "
30 << gradeBook2.getCourseName() << endl;
31 return 0; // indicate successful termination
32 } // end main
|
Name "CS101 Introduction to Programming in C++" exceeds maximum length (25).
Limiting courseName to first 25 characters.
gradeBook1's initial course name is: CS101 Introduction to Pro
gradeBook2's initial course name is: CS102 C++ Data Structures
gradeBook1's course name is: CS101 C++ Programming
gradeBook2's course name is: CS102 C++ Data Structures
|
Lines 18–21 of Fig. 3.17 display the truncated
course name for gradeBook1 (we highlight
this in bold black 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 27–30
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, and so on
Software Engineering Observation 3.5
|
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
private—you must provide appropriate
validity checking and report the
errors. |
Software Engineering Observation 3.6
|
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.15–3.17 simple at
this early point in the book, setCourseName in Fig. 3.16 just prints an appropriate
message on the screen.