const (Constant) Objects and const Member Functions

来源:互联网 发布:男发型设计软件 编辑:程序博客网 时间:2024/05/19 23:17

 

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.


[Page 525]

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 consthelps enforce the principle of least privilege. Attempts to modify the object are caught at compile time rather than causing execution-time errors. Usingconst 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 declaredconst. This is true even for get member functions that do not modify the object. In addition, the compiler does not allow member functions declaredconst 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 keywordconst 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 withconst 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 constructor1112     // set functions13     void setTime( int, int, int ); // set time14     void setHour( int ); // set hour15     void setMinute( int ); // set minute16     void setSecond( int ); // set second1718     // get functions (normally declared const)19     int getHour() const; // return hour       20     int getMinute() const; // return minute   21     int getSecond() const; // return second   2223     // print functions (normally declared const)        24     void printUniversal() const; // print universal time25     void printStandard(); // print standard time (should be const)26  private:27     int hour; // 0 - 23 (24-hour clock format)28     int minute; // 0 - 5929     int second; // 0 - 5930  }; // end class Time3132  #endif
Figure 10.2. Time class member-function definitions, includingconst 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 <iostream> 4  using std::cout; 5 6  #include <iomanip> 7  using std::setfill; 8  using std::setw; 910  #include "Time.h" // include definition of class Time1112  // 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 constructor1920  // set hour, minute and second values21  void Time::setTime( int hour, int minute, int second )22  {23     setHour( hour );24     setMinute( minute );25     setSecond( second );26  } // end function setTime2728  // set hour value29  void Time::setHour( int h )30  {31     hour = ( h >= 0 && h < 24 ) ? h : 0; // validate hour32  } // end function setHour3334  // set minute value35  void Time::setMinute( int m )36  {37     minute = ( m >= 0 && m < 60 ) ? m : 0; // validate minute38  } // end function setMinute3940  // set second value41  void Time::setSecond( int s )42  {43     second = ( s >= 0 && s < 60 ) ? s : 0; // validate second44  } // end function setSecond4546  // return hour value47  int Time::getHour() const // get functions should be const48  {49     return hour;50  } // end function getHour5152  // return minute value53  int Time::getMinute() const54  {55     return minute;56  } // end function getMinute5758  // return second value59  int Time::getSecond() const60  {61     return second;62  } // end function getSecond6364  // print Time in universal-time format (HH:MM:SS)65  void Time::printUniversal() const66  {67     cout << setfill( '0' ) << setw( 2 ) << hour << ":"68        << setw( 2 ) << minute << ":" << setw( 2 ) << second;69  } // end function printUniversal7071  // print Time in standard-time format (HH:MM:SS AM or PM)72  void Time::printStandard() // note lack of const declaration73  {74     cout << ( ( hour == 0 || hour == 12 ) ? 12 : hour % 12 )75        << ":" << setfill( '0' ) << setw( 2 ) << minute76        << ":" << 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 aconst 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 notconst, the compiler uses the non-const version.


An interesting problem arises for constructors and destructors, each of which typically modifies objects. Theconst 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.


[Page 526]

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 classTime of Figs. 9.99.10 by making itsget functions and printUniversal functionconst. In the header file Time.h (Fig. 10.1), lines 1921 and 24 now include keywordconst after each function's parameter list. The corresponding definition of each function inFig. 10.2 (lines 47, 53, 59 and 65, respectively) also specifies keywordconst after each function's parameter list.

Figure 10.3 instantiates twoTime objectsnon-const object wakeUp (line 7) and const objectnoon (line 8). The program attempts to invoke non-const member functionssetHour (line 13) and printStandard (line 20) on the const objectnoon. 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 aconst object (lines 1718). The error messages generated for non-const member functions called on aconst 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.


[Page 527]
Figure 10.3. const objects andconst 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 910                            // OBJECT      MEMBER FUNCTION11     wakeUp.setHour( 18 );  // non-const   non-const1213     noon.setHour( 12 );    // const       non-const1415     wakeUp.getHour();      // non-const   const1617     noon.getMinute();      // const       const18     noon.printUniversal(); // const       const1920     noon.printStandard();  // const       non-const21     return 0;22  } // end main

Borland C++ command-line compiler error messages:

 Warning W8037 fig10_03.cpp 13: Non-const function Time::setHour(int)    called for const object in function main() Warning W8037 fig10_03.cpp 20: Non-const function Time::printStandard()    called for const object in function main()


Microsoft Visual C++.NET compiler error messages:

 C:\cpphtp5_examples\ch10\Fig10_01_03\fig10_03.cpp(13) : error C2662:    'Time::setHour' : cannot convert 'this' pointer from 'const Time' to    'Time &'         Conversion loses qualifiers C:\cpphtp5_examples\ch10\Fig10_01_03\fig10_03.cpp(20) : error C2662:    'Time::printStandard' : cannot convert 'this' pointer from 'const Time' to    'Time &'         Conversion loses qualifiers


GNU C++ compiler error messages:

 fig10_03.cpp:13: error: passing `const Time' as `this' argument of    `void Time::setHour(int)' discards qualifiers fig10_03.cpp:20: error: passing `const Time' as `this' argument of    `void Time::printStandard()' discards qualifiers



[Page 529]

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 theTime constructor (Fig. 10.2, lines 1518) shows that theTime constructor calls another non-const member functionsetTime (lines 2126)to perform the initialization of aTime object. Invoking a non-const member function from the constructor call as part of the initialization of aconst 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 classTime 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 declaredconst.

Initializing a const Data Member with a Member Initializer

The program of Figs. 10.410.6 introduces usingmember initializer syntax. All data memberscan be initialized using member initializer syntax, butconst 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. InChapter 12 when we study inheritance, we will see that base-class portions of derived classes also must be initialized this way.


[Page 531]

The constructor definition (Fig. 10.5, lines 1116) uses amember initializer list to initialize classIncrement'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 andincrement 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 membercount 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 constructor1011     // function addIncrement definition12     void addIncrement()13     {14        count += increment;15     } // end function addIncrement1617     void print() const; // prints count and increment18  private:19     int count;20     const int increment; // const data member21  }; // end class Increment2223  #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 <iostream> 5  using std::cout; 6  using std::endl; 7 8  #include "Increment.h" // include definition of class Increment 910  // constructor11  Increment::Increment( int c, int i )12     : count( c ), // initializer for non-const member        13       increment( i ) // required initializer for const member14  {15     // empty body16  } // end constructor Increment1718  // print count and increment values19  void Increment::print() const20  {21     cout << "count = " << count << ", increment = " << increment << endl;22  } // end function print
Figure 10.6. Invoking an Increment object'sprint and addIncrement member functions.

 1  // Fig. 10.6: fig10_06.cpp 2  // Program to test class Increment. 3  #include <iostream> 4  using std::cout; 5 6  #include "Increment.h" // include definition of class Increment 7 8  int main() 9  {10     Increment value( 10, 5 );1112     cout << "Before incrementing: ";13     value.print();1415     for ( int j = 1; j <= 3; j++ )16     {17        value.addIncrement();18        cout << "After increment " << j << ": ";19        value.print();20     } // end for2122     return 0;23  } // end main

 Before incrementing: count = 10, increment = 5 After increment 1: count = 15, increment = 5 After increment 2: count = 20, increment = 5 After increment 3: count = 25, increment = 5


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 declaredconst, 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.



[Page 532]

Erroneously Attempting to Initialize aconst Data Member with an Assignment

The program ofFigs. 10.710.9 illustrates the compilation errors caused by attempting to initializeconst data member increment with an assignment statement (Fig. 10.8, line 14) in theIncrement constructor's body rather than with a member initializer. Note that line 13 ofFig. 10.8 does not generate a compilation error, becausecount is not declared const. Also note that the compilation errors produced by Microsoft Visual C++.NET refer toint 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 membercount 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 constructor1011     // function addIncrement definition12     void addIncrement()13     {14        count += increment;15     } // end function addIncrement1617     void print() const; // prints count and increment18  private:19     int count;20     const int increment; // const data member21  }; // end class Increment2223  #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 <iostream> 5  using std::cout; 6  using std::endl; 7 8  #include "Increment.h" // include definition of class Increment 910  // constructor; constant member 'increment' is not initialized11  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                                1617  // print count and increment values18  void Increment::print() const19  {20     cout << "count = " << count << ", increment = " << increment << endl;21  } // end function print

Common Programming Error 10.5

Not providing a member initializer for aconst data member is a compilation error.


Software Engineering Observation 10.4

Constant data members (const objects andconst 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 declaredconst. It might seem strange to label this function const, because a program probably will never have aconst Increment object. However, it is possible that a program will have aconst reference to an Increment object or a pointer to const that points to anIncrement object. Typically, this occurs when objects of class Increment are passed to functions or returned from functions. In these cases, only theconst member functions of class Increment can be called through the reference or pointer. Thus, it is reasonable to declare functionprint as constdoing so prevents errors in these situations where anIncrement 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 creatingconst objects of that class or accessing objects of that class through const references or pointers to const. Declaring such member functionsconst does offer a benefit, though. If the member function is inadvertently written to modify the object, the compiler will issue an error message.



[Page 533]

Figure 10.9. Program to test classIncrement 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 <iostream> 4  using std::cout; 5 6  #include "Increment.h" // include definition of class Increment 7 8  int main() 9  {10     Increment value( 10, 5 );1112     cout << "Before incrementing: ";13     value.print();1415     for ( int j = 1; j <= 3; j++ )16     {17       value.addIncrement();18       cout << "After increment " << j << ": ";19       value.print();20     } // end for2122     return 0;23  } // end main

Borland C++ command-line compiler error message:

 Error E2024 Increment.cpp 14: Cannot modify a const object in function    Increment::Increment(int,int)


Microsoft Visual C++.NET compiler error messages:

 C:\cpphtp5_examples\ch10\Fig10_07_09\Increment.cpp(12) : error C2758:    'Increment::increment' : must be initialized in constructor    base/member initializer list          C:\cpphtp5_examples\ch10\Fig10_07_09\Increment.h(20) :             see declaration of 'Increment::increment' C:\cpphtp5_examples\ch10\Fig10_07_09\Increment.cpp(14) : error C2166:    l-value specifies const object


GNU C++ compiler error messages:

 Increment.cpp:12: error: uninitialized member 'Increment::increment' with    'const' type 'const int' Increment.cpp:14: error: assignment of read-only data-member    `Increment::increment'


0 0