Constant data members are useful when you have a class field that you want to initialize but never change. An example of such a member is a unique identification field used to name different objects. Constant member functions are useful when you have constant class values: Only constant member functions can be called on such values.
Static data members serve as class-wide variables for a class. Only one instance of a static data member exists regardless of how many objects are created. If a data member is static, then all objects (of this class type) share access to the one memory location that holds that static data member's value. Static data members exist even if no instances of the class currently exist. From outside the class, public static data members can be accessed directly (object.publicmember or classname::publicmember). Private static data members can only be accessed through public member functions (object.publicMethod()) unless access is granted via a friend declaration. A function member can be declared static; if so, you can use it even when no values of the class exist (classname::staticPublicMethod()). Static member functions have no 'this' pointer and may only access static members.
In this lab we will be using a class that represents natural numbers. Recall that natural numbers are non-negative integers. When a natural number object is created, it will represent a natural number. Various operations are defined to change the value of a natural number object. You will have to ensure that no operation changes a natural number object to an illegal value.
Download and open the workspace for Lab 5. Try compiling the project. You should get an error or warning about the use of the display routine for zero. What message do you get?
error C2662: 'display' : cannot convert 'this' pointer from '____________________________________'
to '________________________________________'
The error occurs on this line: zero.display("Is zero 0? ");
The display method is being applied to the object zero. Notice that zero has been declared as a const Natnum object. Member functions receive a pointer to the object they are applied to (in 'this'). Hence, this must point to a memory location containing a Natnum object (i.e. *this would be a reference (aliais to) a Natnum object that can be modified). Because zero is a const Natnum, such an access would be illegal. The error message states this fact, albeit in slightly different words :)
A member function should always be declared as const if it is not supposed to affect the value of *this. Constant member functions are useful because they are the only member functions that can be applied to a constant object (e.g. constObject.constMethod();).
Make the display function a constant member function by inserting const as follows: (1) Put const in the prototype, after the parameters and before the terminating semicolon:
(2) Similarly, put a const in the implementation of display, after the parameters and before the left curly brace that begins the function body. Recompile the project; it should now compile correctly and print an appropriate value for zero.void display(const char *message = "") const;
Uncomment the 4 lines in the main program following the display of zero. You should get compile errors - some of which are similar to what you just saw above. Make the required corrections in Natnum.cpp and Natnum.h (add the word const in about 8 places) to eliminate all compile errors. In addition, complete the bodies of the add function and the subtract method. These currently return a dummy result. The return value is a Natnum - you will probably want to use the conversion constructor to create the return value.
Note that the add function is not a member of Natnum, so it does not have access to the v field of its arguments. Instead, it will have to use the public value() member function. (If the add function absolutely had to have access to the v field, it could have been made a friend function of Natnum by adding a "friend" declaration to the Natnum class definition.)
You should be able to explain how add and subtract never return an "illegal" Natnum object, even though the sum or difference might be illegal. By the way, you really do not need any if statements in add and subtract!
Can you explain what part of the main program causes the message
Conversion constructor value 2147483647
to appear in the output?
________________________________________________________________________________
________________________________________________________________________________
In this task we will add "chainable" versions of assignment, increment, and decrement to Natnum. Note that the main program contains a line
x.asgn(4).incr().decr().incr(3).decr(2).display("Is 5? ");
The intent is that x should be set to 4, then 5 (increment by 1), then
4 (decrement by 1), then 7 (increment by 3), then 5 (decrement by 2), and then
be displayed. For the line to work correctly, the asgn,
incr,
and decr routines should each return a reference to the Natnum they
operate on. This should be done by having each return *this;
after they set, increment, or decrement the v field. For example,
the assignment routine definition should be
Natnum & Natnum :: asgn(int r) {
v = (r>=0?r:0);
return *this;
}
In Natnum.cpp, add definitions for asgn, incr, and decr to
the implementation. The headers are already present in the file, but are
commented out. Compile and run the program. You should wind up
with x indeed having the value 5. Once you get the routines working, make this change: Change the prototype and header of the asgn routine to Natnum Natnum::asgn(.... You are changing the return type from Natnum & (a reference to the already existing Natnum) to Natnum (a copy of the Natnum value).
Compile and run the project. You should find that although 5 is still the result of the long chain of calls, x is not set to 5. Why is this?
________________________________________________________________________________
________________________________________________________________________________
________________________________________________________________________________
Change asgn back to its original form - it should return a reference to a Natnum, not a copy of a Natnum.
Be sure that the checks (in asgn, incr, and decr) for going out of range are working. Some of the output tests illegal uses of these three methods.
Should any of the methods (asgn, incr, or decr) be declared as const methods? ______ Why or why not?
________________________________________________________________________________
In this task we will add a static data item to the Natnum class. This data item will be used to generate unique id numbers for each Natnum instance. This way we'll be able to see exactly which Natnum values are created and destroyed, and when.
Try compiling. You should find that a static method does not exist, so the linking step should fail. This method must be implemented in the Natnum implementation. Before going there, open the Natnum header file and uncomment the two data members (one a const int that will hold the unique ID for each Natnum object, the other a shared int that will be used to generate unique ID's). There will be one last_id_used, but many id's.
At the end of Natnum.cpp, uncomment the definitions of the two get methods for the id and last_id_used. One is static. One is const. (Actually, the static method should be const as well, but C++ does not allow static methods to be declared as const, so we have to omit that - remember that the intent of a const method is to prohibit changes to an object - but static methods are not applied to objects, only to the static variables in the class - they are really like normal functions that have access to the static variables declared in the class).
Before compiling, add an initializer for the new data member, id. You can do this in the initializer list (both constructors) - you should set it to the value of the ++last_id_used (last_id_used will then be autoincremented).
Build the project. From the error message, you will learn that the _____________ keyword is not used in the function definition - only in its declaration (in the header file). Remove the keyword from the function definition and build the project again. This time you should get the following link error:
Natnum.obj : error LNK2001: unresolved external symbol "private: static int Natnum::last_id_used"
Static members must be defined (given a value) in one location in the project. For a static member of Natnum, the appropriate place is in the Natnum.cpp file. Why would it be wrong to put the definition in the Natnum.h file?
__________________________________________________________________________
__________________________________________________________________________
Locate the definition of last_id_used and uncomment it, then build again. If all goes well, you can now execute the program.
Now study the main program. For this task, most of the body of the main program is surrounded by curly braces. The braces group together the contained statements and declarations, and when control hits the right brace, the variables declared between the braces are all deallocated and destructed.
After the brace, no Natnum values exist, so only the special syntax Natnum::get_last_id_used() can be used to call the get_last_id function. This syntax is only available for functions declared static in the class definition. Try adding the line "cout << Natnum::get_id();" to the main program, just after the print of Natnum::get_last_id_used(). What error message do you get on recompiling?
_________________________________________________________________________
________________________________________________________________________
Comment out the line so it does not cause an error.
For the next part of this task, let's use our unique id numbers to help study the reference result-returning functions from the previous task. At this point, the asgn routine should be returning a reference to a Natnum. Add these statements just above the return statement in main:
Natnum x(27);
Natnum y;
x.display("This is x ");
y.display("This is y ");
Run the program and record the ID numbers for x and y: x: _____ y: _____
Now add the statement y.asgn(x.value()) before the display. Record the ID numbers for x and y.
x: _____ y: _____ Were any additional objects created in the process? _____ How do you know?
________________________________________________________________________
Change the header and prototype for the asgn routine: remove the ampersand from the result type. Recompile and rerun the program. Record the ID numbers for x and y:
x: _____ y: _____ Were any additional objects created in the process? _____ How do you know?
________________________________________________________________________
Change the return type of asgn so it once again returns a reference (in the header too).
For the final part of this task, let's use our unique id numbers to help study the workings of the copy constructor and assignment operator. Change the statement y.asgn(x.value()); to y = x; (using C's assignment operator rather than our asgn method). Build and run the project. Record the ID numbers:
x: _____ y: _____ You should find that the id numbers are equal: Default assignment copies the bytes of one object into the bytes of another. In this case, the data member id is overwritten by x's id! The default assignment operator does not always do what you want it to do.
Comment out the assignment statement - we will not need it further. Also change the declaration of y to use the copy constructor (copying x). Build and run the project. Now what id numbers do x and y have when they are created and displayed?
x: _____ y: _____ (They should be distinct)
Next, in the Natnum class definition, comment out the copy constructor prototype. In the Natnum implementation, comment out the copy constructor definition. Build and run the project. Now what id numbers do x and y have when they are created and displayed?
x: _____ y: _____
You should find that the id numbers are equal: If you don't supply a copy constructor explicitly, then C++ uses a default copy constructor that simply copies the individual bytes of the original to make the copy. When y is created as a copy of x, the id field of x is copied into the id field of y. This is similar to the incorrect behavior of default assignment.
Moral? Beware of default copy constructors and the behavior of the default assignment operator- even when no dynamic storage is involved. Add dynamic storage allocation, or any type of pointer members, and these default operations become even more dangerous.
You're done!. Remember to add your name to the files you revised. Please turn in this sheet and the printout of your revised program. Thank you.