Adding const qualifiers to objects that should not be modified is almost always a good idea. Almost.
C++ allows classes to define constant non-static data members, which seems a handy enough feature at first glance. If you are thinking of using constant non-static data members in your next design, this article may help you figure out if it's really a good idea. As I have found, sometimes it isn't.
Consider a class designed to store information about employees. This class might contain fields that are conceptually constant for example, date of birth. At first it seems logical to make this field constant in a C++ class Employee:
class Employee{ public : Employee(const string &n, const unsigned long &d): name(n), dob(d){} // possible other members private : string name; const unsigned long dob; // possible other data members }
This class has a few limitations. First, although the compiler can generate a correctly-working copy constructor (which I neglected to define), it cannot generate an assignment operator. If a program creates two Employee objects e1 and e2, the statement e1 = e2 will cause a compiler diagnostic. This assignment is invalid because it implicitly attempts to change the value of the const member e1.dob. The Watcom 10.5, Borland 4.5 and Microsoft 4.0 [1]
compilers all generate an error when such an assignment is attempted.
Another limitation is that it is difficult to provide a useful default constructor for this class. A default constructor is essential if you want to create, say, arrays of Employee objects. Defining a default constructor is a little bit tricky; the main thing to remember is that const non-static data members get initialized in the constructor's initialization list and you must initialize all of them. The default constructor must set the dob field to some value, and this value cannot be subsequently altered. So the constructed array will be practically useless.
Given the questionable benefit of having a default constructor, the class designer may elect not to define one. But this class also lacks an assignment operator, and the limitations of this class are quite severe. The user program cannot create heap-based arrays of the class's objects, nor can it use procedures that need to create temporary objects.
One solution is to create a special-purpose assignment operator. To compile without diagnostics, such an assignment operator must follow one of two approaches: 1) avoid attempting to modify the contents of any const data members 2) "cast away const" on the const data members before modifying them.
The first approach solves none of the problems mentioned above, so I do not consider it further. The second approach may be implemented as follows:
Employee& Employee::operator=( const Employee &op2){ if(&op2 != this){ name = op2.name; //cast away constantness in // order to assign dob (unsigned long&)dob = op2.dob; } return *this; }
Class Employee now has a copy constructor, an assignment operator, and a default constructor, which will perform as expected.
Evaluation
At this point it's a good idea to reexamine the wisdom of making Employee::dob a constant in the first place. Note that dob is already defined as private, so making it const is unnecessary to protect it from modification by users. dob's constness is necessary only to protect it from code internal to the Employee class or from friends of Employee. These entities can still modify the field by casting away constness, just as the assignment operator did, so the safety afforded is not absolute. Is the modicum of safety achieved worth it? As the class stands, I think not, but if the class were very complex and contained many member functions perhaps it would be worth the effort.
In conclusion, constant non-static data fields create complications, and in most cases it is better not to design such fields into a class. The designer can adequately protect these fields from undesired write access by making them private. In cases where the class is very large and complex you might consider making certain fields constant and coding an assignment operator which casts away constness. In rare cases you might even want the limitations of a class that has constant non static data and no assignment operator or default constructor. If you need to go this far you may also need to suppress a compiler generated copy constructor by supplying the class with a private, non-defined copy constructor.
Acknowledgement
[1] Thanks to Art Doglione of Scottsdale, Az for testing this on the Microsoft compiler.
Jack Hawes has a degree in mathematics from Arizona State University. He has been programming for 12 years. He may be reached at [email protected].