const (Constant) Objects and const Member Functions
We have emphasized the principle of least privilege as one of the most fundamental principles of good software engineering. Let us see how this principle applies to objects.
Some objects need to be modifiable and some do not. The programmer may use keyword const to specify that an object is not modifiable and that any attempt to modify the object should result in a compilation error. The statement
const Time noon( 12, 0, 0 );
declares a const object noon of class Time and initializes it to 12 noon.
Software Engineering Observation 10.1
Declaring an object as const helps enforce the principle of least privilege. Attempts to modify the object are caught at compile time rather than causing execution-time errors. Using const properly is crucial to proper class design, program design and coding. |
Performance Tip 10.1
Declaring variables and objects const can improve performancetoday's sophisticated optimizing compilers can perform certain optimizations on constants that cannot be performed on variables. |
C++ compilers disallow member function calls for const objects unless the member functions themselves are also declared const. This is true even for get member functions that do not modify the object. In addition, the compiler does not allow member functions declared const to modify the object.
A function is specified as const both in its prototype (Fig. 10.1; lines 1924) and in its definition (Fig. 10.2; lines 47, 53, 59 and 65) by inserting the keyword const after the function's parameter list and, in the case of the function definition, before the left brace that begins the function body.
Figure 10.1. Time class definition with const member functions.
(This item is displayed on page 526 in the print version)
1 // Fig. 10.1: Time.h 2 // Definition of class Time. 3 // Member functions defined in Time.cpp. 4 #ifndef TIME_H 5 #define TIME_H 6 7 class Time 8 { 9 public: 10 Time( int = 0, int = 0, int = 0 ); // default constructor 11 12 // set functions 13 void setTime( int, int, int ); // set time 14 void setHour( int ); // set hour 15 void setMinute( int ); // set minute 16 void setSecond( int ); // set second 17 18 // get functions (normally declared const) 19 int getHour() const; // return hour 20 int getMinute() const; // return minute 21 int getSecond() const; // return second 22 23 // print functions (normally declared const) 24 void printUniversal() const; // print universal time 25 void printStandard(); // print standard time (should be const) 26 private: 27 int hour; // 0 - 23 (24-hour clock format) 28 int minute; // 0 - 59 29 int second; // 0 - 59 30 }; // end class Time 31 32 #endif |
Figure 10.2. Time class member-function definitions, including const member functions.
(This item is displayed on pages 527 - 528 in the print version)
1 // Fig. 10.2: Time.cpp 2 // Member-function definitions for class Time. 3 #include 4 using std::cout; 5 6 #include 7 using std::setfill; 8 using std::setw; 9 10 #include "Time.h" // include definition of class Time 11 12 // constructor function to initialize private data; 13 // calls member function setTime to set variables; 14 // default values are 0 (see class definition) 15 Time::Time( int hour, int minute, int second ) 16 { 17 setTime( hour, minute, second ); 18 } // end Time constructor 19 20 // set hour, minute and second values 21 void Time::setTime( int hour, int minute, int second ) 22 { 23 setHour( hour ); 24 setMinute( minute ); 25 setSecond( second ); 26 } // end function setTime 27 28 // set hour value 29 void Time::setHour( int h ) 30 { 31 hour = ( h >= 0 && h < 24 ) ? h : 0; // validate hour 32 } // end function setHour 33 34 // set minute value 35 void Time::setMinute( int m ) 36 { 37 minute = ( m >= 0 && m < 60 ) ? m : 0; // validate minute 38 } // end function setMinute 39 40 // set second value 41 void Time::setSecond( int s ) 42 { 43 second = ( s >= 0 && s < 60 ) ? s : 0; // validate second 44 } // end function setSecond 45 46 // return hour value 47 int Time::getHour() const // get functions should be const 48 { 49 return hour; 50 } // end function getHour 51 52 // return minute value 53 int Time::getMinute() const 54 { 55 return minute; 56 } // end function getMinute 57 58 // return second value 59 int Time::getSecond() const 60 { 61 return second; 62 } // end function getSecond 63 64 // print Time in universal-time format (HH:MM:SS) 65 void Time::printUniversal() const 66 { 67 cout << setfill( '0' ) << setw( 2 ) << hour << ":" 68 << setw( 2 ) << minute << ":" << setw( 2 ) << second; 69 } // end function printUniversal 70 71 // print Time in standard-time format (HH:MM:SS AM or PM) 72 void Time::printStandard() // note lack of const declaration 73 { 74 cout << ( ( hour == 0 || hour == 12 ) ? 12 : hour % 12 ) 75 << ":" << setfill( '0' ) << setw( 2 ) << minute 76 << ":" << setw( 2 ) << second << ( hour < 12 ? " AM" : " PM" ); 77 } // end function printStandard |
Common Programming Error 10.1
Defining as const a member function that modifies a data member of an object is a compilation error. |
Common Programming Error 10.2
Defining as const a member function that calls a non-const member function of the class on the same instance of the class is a compilation error. |
Common Programming Error 10.3
Invoking a non-const member function on a const object is a compilation error. |
Software Engineering Observation 10.2
A const member function can be overloaded with a non-const version. The compiler chooses which overloaded member function to use based on the object on which the function is invoked. If the object is const, the compiler uses the const version. If the object is not const, the compiler uses the non-const version. |
An interesting problem arises for constructors and destructors, each of which typically modifies objects. The const declaration is not allowed for constructors and destructors. A constructor must be allowed to modify an object so that the object can be initialized properly. A destructor must be able to perform its termination housekeeping chores before an object's memory is reclaimed by the system.
Common Programming Error 10.4
Attempting to declare a constructor or destructor const is a compilation error. |
Defining and Using const Member Functions
The program of Figs. 10.110.3 modifies class Time of Figs. 9.99.10 by making its get functions and printUniversal function const. In the header file Time.h (Fig. 10.1), lines 1921 and 24 now include keyword const after each function's parameter list. The corresponding definition of each function in Fig. 10.2 (lines 47, 53, 59 and 65, respectively) also specifies keyword const after each function's parameter list.
Figure 10.3 instantiates two Time objectsnon-const object wakeUp (line 7) and const object noon (line 8). The program attempts to invoke non-const member functions setHour (line 13) and printStandard (line 20) on the const object noon. In each case, the compiler generates an error message. The program also illustrates the three other member-function-call combinations on objectsa non-const member function on a non-const object (line 11), a const member function on a non-const object (line 15) and a const member function on a const object (lines 1718). The error messages generated for non-const member functions called on a const object are shown in the output window. Notice that, although some current compilers issue only warning messages for lines 13 and 20 (thus allowing this program to be executed), we consider these warnings to be errorsthe ANSI/ISO C++ standard disallows the invocation of a non-const member function on a const object.
Figure 10.3. const objects and const member functions.
(This item is displayed on pages 528 - 529 in the print version)
1 // Fig. 10.3: fig10_03.cpp 2 // Attempting to access a const object with non-const member functions. 3 #include "Time.h" // include Time class definition 4 5 int main() 6 { 7 Time wakeUp( 6, 45, 0 ); // non-constant object 8 const Time noon( 12, 0, 0 ); // constant object 9 10 // OBJECT MEMBER FUNCTION 11 wakeUp.setHour( 18 ); // non-const non-const 12 13 noon.setHour( 12 ); // const non-const 14 15 wakeUp.getHour(); // non-const const 16 17 noon.getMinute(); // const const 18 noon.printUniversal(); // const const 19 20 noon.printStandard(); // const non-const 21 return 0; 22 } // end main Borland C++ command-line compiler error messages:
Microsoft Visual C++.NET compiler error messages:
GNU C++ compiler error messages:
|
Notice that even though a constructor must be a non-const member function (Fig. 10.2, lines 1518), it can still be used to initialize a const object (Fig. 10.3, line 8). The definition of the Time constructor (Fig. 10.2, lines 1518) shows that the Time constructor calls another non-const member functionsetTime (lines 2126)to perform the initialization of a Time object. Invoking a non-const member function from the constructor call as part of the initialization of a const object is allowed. The "const ness" of a const object is enforced from the time the constructor completes initialization of the object until that object's destructor is called.
Also notice that line 20 in Fig. 10.3 generates a compilation error even though member function printStandard of class Time does not modify the object on which it is invoked. The fact that a member function does not modify an object is not sufficient to indicate that the function is constant functionthe function must explicitly be declared const.
Initializing a const Data Member with a Member Initializer
The program of Figs. 10.410.6 introduces using member initializer syntax. All data members can be initialized using member initializer syntax, but const data members and data members that are references must be initialized using member initializers. Later in this chapter, we will see that member objects must be initialized this way as well. In Chapter 12 when we study inheritance, we will see that base-class portions of derived classes also must be initialized this way.
The constructor definition (Fig. 10.5, lines 1116) uses a member initializer list to initialize class Increment's data membersnon-const integer count and const integer increment (declared in lines 1920 of Fig. 10.4). Member initializers appear between a constructor's parameter list and the left brace that begins the constructor's body. The member initializer list (Fig. 10.5, lines 1213) is separated from the parameter list with a colon (:). Each member initializer consists of the data member name followed by parentheses containing the member's initial value. In this example, count is initialized with the value of constructor parameter c and increment is initialized with the value of constructor parameter i. Note that multiple member initializers are separated by commas. Also, note that the member initializer list executes before the body of the constructor executes.
Figure 10.4. Increment class definition containing non-const data member count and const data member increment.
(This item is displayed on page 530 in the print version)
1 // Fig. 10.4: Increment.h 2 // Definition of class Increment. 3 #ifndef INCREMENT_H 4 #define INCREMENT_H 5 6 class Increment 7 { 8 public: 9 Increment( int c = 0, int i = 1 ); // default constructor 10 11 // function addIncrement definition 12 void addIncrement() 13 { 14 count += increment; 15 } // end function addIncrement 16 17 void print() const; // prints count and increment 18 private: 19 int count; 20 const int increment; // const data member 21 }; // end class Increment 22 23 #endif |
Figure 10.5. Member initializer used to initialize a constant of a built-in data type.
(This item is displayed on page 530 in the print version)
1 // Fig. 10.5: Increment.cpp 2 // Member-function definitions for class Increment demonstrate using a 3 // member initializer to initialize a constant of a built-in data type. 4 #include 5 using std::cout; 6 using std::endl; 7 8 #include "Increment.h" // include definition of class Increment 9 10 // constructor 11 Increment::Increment( int c, int i ) 12 : count( c ), // initializer for non-const member 13 increment( i ) // required initializer for const member 14 { 15 // empty body 16 } // end constructor Increment 17 18 // print count and increment values 19 void Increment::print() const 20 { 21 cout << "count = " << count << ", increment = " << increment << endl; 22 } // end function print |
Figure 10.6. Invoking an Increment object's print and addIncrement member functions.
1 // Fig. 10.6: fig10_06.cpp 2 // Program to test class Increment. 3 #include 4 using std::cout; 5 6 #include "Increment.h" // include definition of class Increment 7 8 int main() 9 { 10 Increment value( 10, 5 ); 11 12 cout << "Before incrementing: "; 13 value.print(); 14 15 for ( int j = 1; j <= 3; j++ ) 16 { 17 value.addIncrement(); 18 cout << "After increment " << j << ": "; 19 value.print(); 20 } // end for 21 22 return 0; 23 } // end main
|
Software Engineering Observation 10.3
A const object cannot be modified by assignment, so it must be initialized. When a data member of a class is declared const, a member initializer must be used to provide the constructor with the initial value of the data member for an object of the class. The same is true for references. |
Erroneously Attempting to Initialize a const Data Member with an Assignment
The program of Figs. 10.710.9 illustrates the compilation errors caused by attempting to initialize const data member increment with an assignment statement (Fig. 10.8, line 14) in the Increment constructor's body rather than with a member initializer. Note that line 13 of Fig. 10.8 does not generate a compilation error, because count is not declared const. Also note that the compilation errors produced by Microsoft Visual C++.NET refer to int data member increment as a "const object." The ANSI/ISO C++ standard defines an "object" as any "region of storage." Like instances of classes, fundamental-type variables also occupy space in memory, so they are often referred to as "objects."
Figure 10.7. Increment class definition containing non-const data member count and const data member increment.
(This item is displayed on pages 532 - 533 in the print version)
1 // Fig. 10.7: Increment.h 2 // Definition of class Increment. 3 #ifndef INCREMENT_H 4 #define INCREMENT_H 5 6 class Increment 7 { 8 public: 9 Increment( int c = 0, int i = 1 ); // default constructor 10 11 // function addIncrement definition 12 void addIncrement() 13 { 14 count += increment; 15 } // end function addIncrement 16 17 void print() const; // prints count and increment 18 private: 19 int count; 20 const int increment; // const data member 21 }; // end class Increment 22 23 #endif |
Figure 10.8. Erroneous attempt to initialize a constant of a built-in data type by assignment.
(This item is displayed on page 533 in the print version)
1 // Fig. 10.8: Increment.cpp 2 // Attempting to initialize a constant of 3 // a built-in data type with an assignment. 4 #include 5 using std::cout; 6 using std::endl; 7 8 #include "Increment.h" // include definition of class Increment 9 10 // constructor; constant member 'increment' is not initialized 11 Increment::Increment( int c, int i ) 12 { 13 count = c; // allowed because count is not constant 14 increment = i; // ERROR: Cannot modify a const object 15 } // end constructor Increment 16 17 // print count and increment values 18 void Increment::print() const 19 { 20 cout << "count = " << count << ", increment = " << increment << endl; 21 } // end function print |
Common Programming Error 10.5
Not providing a member initializer for a const data member is a compilation error. |
Software Engineering Observation 10.4
Constant data members (const objects and const variables) and data members declared as references must be initialized with member initializer syntax; assignments for these types of data in the constructor body are not allowed. |
Note that function print (Fig. 10.8, lines 1821) is declared const. It might seem strange to label this function const, because a program probably will never have a const Increment object. However, it is possible that a program will have a const reference to an Increment object or a pointer to const that points to an Increment object. Typically, this occurs when objects of class Increment are passed to functions or returned from functions. In these cases, only the const member functions of class Increment can be called through the reference or pointer. Thus, it is reasonable to declare function print as constdoing so prevents errors in these situations where an Increment object is treated as a const object.
Error-Prevention Tip 10.1
Declare as const all of a class's member functions that do not modify the object in which they operate. Occasionally this may seem inappropriate, because you will have no intention of creating const objects of that class or accessing objects of that class through const references or pointers to const. Declaring such member functions const does offer a benefit, though. If the member function is inadvertently written to modify the object, the compiler will issue an error message. |
Figure 10.9. Program to test class Increment generates compilation errors.
(This item is displayed on pages 533 - 534 in the print version)
1 // Fig. 10.9: fig10_09.cpp 2 // Program to test class Increment. 3 #include 4 using std::cout; 5 6 #include "Increment.h" // include definition of class Increment 7 8 int main() 9 { 10 Increment value( 10, 5 ); 11 12 cout << "Before incrementing: "; 13 value.print(); 14 15 for ( int j = 1; j <= 3; j++ ) 16 { 17 value.addIncrement(); 18 cout << "After increment " << j << ": "; 19 value.print(); 20 } // end for 21 22 return 0; 23 } // end main Borland C++ command-line compiler error message:
Microsoft Visual C++.NET compiler error messages:
GNU C++ compiler error messages:
|