- •Introduction
- •Saving Time with This Book
- •Conventions Used in This Book
- •Part II: Working with the Pre-Processor
- •Part III: Types
- •Part IV: Classes
- •Part V: Arrays and Templates
- •Part VI: Input and Output
- •Part VII: Using the Built-in Functionality
- •Part VIII: Utilities
- •Part IX: Debugging C++ Applications
- •Part X: The Scary (or Fun!) Stuff
- •Icons Used in This Book
- •Creating and Implementing an Encapsulated Class
- •Creating a Mailing-List Application
- •Testing the Mailing-List Application
- •Customizing a Class with Polymorphism
- •Testing the Virtual Function Code
- •Why Do the Destructors Work?
- •Delayed Construction
- •The cDate Class
- •Testing the cDate Class
- •Creating the Header File
- •Testing the Header File
- •The Assert Problem
- •Fixing the Assert Problem
- •Using the const Construct
- •Identifying the Errors
- •Fixing the Errors
- •Fixing What Went Wrong with the Macro
- •Using Macros Appropriately
- •Using the sizeof Function
- •Evaluating the Results
- •Using sizeof with Pointers
- •Implementing the Range Class
- •Testing the Range Class
- •Creating the Matrix Class
- •Matrix Operations
- •Multiplying a Matrix by a Scalar Value
- •Multiplying a Matrix by Scalar Values, Take 2
- •Testing the Matrix Class
- •Implementing the Enumeration Class
- •Testing the Enumeration Class
- •Implementing Structures
- •Interpreting the Output
- •Defining Constants
- •Testing the Constant Application
- •Using the const Keyword
- •Illustrating Scope
- •Interpreting the Output
- •Using Casts
- •Addressing the Compiler Problems
- •Testing the Changes
- •Implementing Member-Function Pointers
- •Updating Your Code with Member-Function Pointers
- •Testing the Member Pointer Code
- •Customizing Functions We Wrote Ourselves
- •Testing the Default Code
- •Fixing the Problem
- •Testing the Complete Class
- •Implementing Virtual Inheritance
- •Correcting the Code
- •Rules for Creating Overloaded Operators
- •Using Conversion Operators
- •Using Overloaded Operators
- •Testing the MyString Class
- •Rules for Implementing new and delete Handlers
- •Overloading new and delete Handlers
- •Testing the Memory Allocation Tracker
- •Implementing Properties
- •Testing the Property Class
- •Implementing Data Validation with Classes
- •Testing Your SSN Validator Class
- •Creating the Date Class
- •Testing the Date Class
- •Some Final Thoughts on the Date Class
- •Creating a Factory Class
- •Testing the Factory
- •Enhancing the Manager Class
- •Implementing Mix-In Classes
- •Testing the Template Classes
- •Implementing Function Templates
- •Creating Method Templates
- •Using the Vector Class
- •Creating the String Array Class
- •Working with Vector Algorithms
- •Creating an Array of Heterogeneous Objects
- •Creating the Column Class
- •Creating the Row Class
- •Creating the Spreadsheet Class
- •Testing Your Spreadsheet
- •Working with Streams
- •Testing the File-Reading Code
- •Creating the Test File
- •Reading Delimited Files
- •Testing the Code
- •Creating the XML Writer
- •Testing the XML Writer
- •Creating the Configuration-File Class
- •Setting Up Your Test File
- •Building the Language Files
- •Creating an Input Text File
- •Reading the International File
- •Testing the String Reader
- •Creating a Translator Class
- •Testing the Translator Class
- •Creating a Virtual File Class
- •Testing the Virtual File Class
- •Using the auto_ptr Class
- •Creating a Memory Safe Buffer Class
- •Throwing and Logging Exceptions
- •Dealing with Unhandled Exceptions
- •Re-throwing Exceptions
- •Creating the Wildcard Matching Class
- •Testing the Wildcard Matching Class
- •Creating the URL Codec Class
- •Testing the URL Codec Class
- •Testing the Rot13 Algorithm
- •Testing the XOR Algorithm
- •Implementing the transform Function to Convert Strings
- •Testing the String Conversions
- •Implementing the Serialization Interface
- •Creating the Buffer Class
- •Testing the Buffer Class
- •Creating the Multiple-Search-Path Class
- •Testing the Multiple-Search-Path Class
- •Testing the Flow Trace System
- •The assert Macro
- •Logging
- •Testing the Logger Class
- •Design by Contract
- •Adding Logging to the Application
- •Making Functions Inline
- •Avoiding Temporary Objects
- •Passing Objects by Reference
- •Choosing Initialization Instead of Assignment
- •Learning How Code Operates
- •Testing the Properties Class
- •Creating the Locking Mechanism
- •Testing the Locking Mechanism
- •Testing the File-Guardian Class
- •Implementing the Complex Class
- •Creating the Conversion Code
- •Testing the Conversion Code
- •A Sample Program
- •Componentizing
- •Restructuring
- •Specialization
- •Index
15 Understanding
Constants
Technique
Save Time By
Exploring the uses of constants
Defining constants
Implementing constants
Using the const keyword
Pity the poor, misunderstood C++ constant — there are so very many ways to use it, yet so few of them are really understood. By using the constant (or const statement) construct in your code, your
applications can be made safer, more readable, and more efficient. Yet, programmers are often so overwhelmed by the incredible number of different ways in which they can use constants that they simply avoid the construct completely, allowing the compiler to pick and choose what can change and what cannot in the application. A word to the wise: Allowing the compiler to make choices for you is very rarely a good idea.
Constants provide a way to self-document your code, as well as a simple way to locate all of the definitions of values in your program. By utilizing constants for your data values, you can make quick, easy, simultaneous changes across the scope of your application. In addition, you can enforce what does and does not change in the methods and functions of your application by using the const keyword.
In this technique I explore the various possibilities for working with constants in C++ and what they mean to you as an application developer.
Defining Constants
To best understand how constants work and how you can utilize them in your application, the following steps show you a simple example of
defining various kinds of constants. We will be creating a file that contains constants for use as whole numbers, floating point numbers, and character strings. You will see how a constant can directly replace a #define value, as well as how the compiler can be used to do type-safe checking of constant assignments.
1. In the code editor of your choice, create a new file to hold the code for the implementation of the source file.
In this example, the file is named ch15.cpp, although you can use whatever you choose.
78 Technique 15: Understanding Constants
2. Type the code from Listing 15-1 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 15-1: THE CONSTANTS AND THEIR DEFINITIONS
#include <stdio.h> #include <stdlib.h>
//This is a constant value that can be used
//in place of a #define value
const int MaxValues = 100;
//Unlike the #define, you can use a const
//for typesafe constants
const char *StringName = “Matt Telles”; const double Cost = 100.35;
const long MaxLong = 1000000;
You can define constants of pretty much any shape or size. Constants can be numbers, strings, characters, or floats. More importantly, the compiler will check for type safety when you assign a constant to another value. For example, you’re permitted to assign string values to string constants, like this:
string myString = StringName;
You are not, however, permitted to assign MaxLong values to integers, which would look like this:
int iVal = MaxLong; // The compiler will complain.
3. Save the source-code file.
Of course (so far), all adding the constants has really done is give us another way to replace the #define statement in our application. The real meat of the C++ constant is giving the compiler directives in your functions and classes, as I show you in the next section.
Implementing Constant
Variables
The const statement can also be used to tell the compiler that something is not permitted to change, as we will see in this simple technique that relies on another facet of the const keyword. Follow these steps to see how it all works:
1. Append the code from Listing 15-2 to your source-code file.
LISTING15-2: A FUNCTION WITH AN IMMUTABLE ARGUMENT
//Functions can take constant arguments, allowing them
//to guarantee that values don’t change.
int func( const int& x )
{
//We can do this int z = x;
//We cannot do this. Compile Error:
//x = 10; |
1 |
return x; |
|
}
This function accepts a single argument of type const int reference. The const modifier indicates to the compiler that the input argument cannot be modified. If you were to uncomment the line marked 1, you would see the compiler generate an error telling you that you cannot modify a constant reference.
This example looks at a function that accepts a single-integer argument, the constant x. We are informing both the user of the function and the compiler that this function will never change the value of that argument, even if it is passed in by reference. This information is very important to the compiler; with this knowledge it can optimize your function so that it does not have to pop the
|
Implementing Constant Variables |
79 |
|
value back off the stack and insure that the mem- |
because the compiler would never allow you to |
||
ory location that it is using will not change. This |
write this: |
|
|
is possible because this value can be thrown |
|
|
|
away after the function is done; after all, it could |
int myVal = func(3); |
|
|
not possibly have changed. |
When you define the input argument as a con- |
||
|
|||
Proper use of the const modifier allows the |
stant, however, the compiler is now aware that |
||
the actual memory location that’s holding your |
|||
compiler to generate closer-to-optimal code |
|||
and generate better warnings, so you can |
value (in this case, 3) will not change, and it will |
||
write better applications and have fewer errors |
allow you to pass in the integer value without |
||
to fix in the debugging phase. |
first assigning it to anything. This arrangement |
||
|
saves a few CPU cycles and some memory — and |
||
Furthermore, because we know that the input is |
you don’t have to write some silly code that |
|
|
doesn’t really do anything. |
|
||
a constant, we can pass in values that are con- |
|
||
2. Using your code editor, append the code from |
|||
stant. For example, if the reference was not a |
|||
constant, you’d have to write this: |
Listing 15-3 to your source-code file. |
|
int x = 3; |
This technique illustrates how you can use the |
|
int myVal = func( x ); |
||
const keyword within a class to accomplish the |
||
|
||
|
same things that it does outside of a class listing. |
|
LISTING 15-3: CONSTANTS IN CLASSES |
|
|
|
|
|
// Classes can have constants in them |
|
|
const int MaxEntries = 10; |
|
|
class Foo |
|
|
{ |
|
|
int entries[ MaxEntries ]; |
|
public:
//You can pass in constant references to arguments Foo()
{
}
Foo( const Foo& aCopy )
{
for ( int i=0; i<MaxEntries; ++i ) entries[i] = aCopy.entries[i];
}
//You can return constant references from methods const int * getEntries()
{
return &entries[0];
}
//You can indicate that a method will NOT change
//anything in the object
int getEntry( int index ) const
{
return entries[index];
(continued)
80 Technique 15: Understanding Constants
LISTING 15-3 (continued)
}
//The two can be combined to say that the return value
//cannot be changed and the object will not change const int& getAConstEntry( int index ) const
{
return entries[index];
}
};
3. Save the source-code file and close the code editor.
As you can see, the const construct is quite versatile. It can be used to indicate a value that is used to replace a number in your code. It can be used to indicate a return value that cannot be changed. It can indicate that a class method accepts an argument that it doesn’t change, such as the Copy constructor. Can you imagine writing a Copy constructor that changed the object it copied? That would be a little strange to say the least. Imagine writing something like this:
Foo f1 = f;
Then imagine having the f object change out from under you — talk about your basic debugging nightmare. For this reason, it’s customary to use a const reference, indicating that you won’t change the object being copied. In the same manner, we can pass in values to methods and assure the user that they won’t be copied (as in the func function we looked at earlier in Listing 15-2).
Of course, if you can take an input value and assure the user that you will not change it, then the quid pro quo argument is that you must be able to give someone back a value and make sure that they don’t change it. This is called returning a const reference. For example, if you have an internal variable, you could create a reference method that gave it back, but only in a read-only fashion. That is what the getEntries method does in Listing 15-3. It returns a const pointer that makes sure that the user doesn’t change anything in the program that calls the object.
Finally, you can tell the user that the method you are calling will never change the object. To do so, you simply append the const keyword at the end of the method, which allows your method to be called on const objects. Doing so also allows the compiler to avoid the overhead of having to make copies of objects and such. If the object cannot be changed via the method, there is no reason to worry about making the memory location static.
Testing the Constant Application
After you create the class, you should create a test driver that not only ensures that your code is correct, but also shows people how to use your code.
1. In the code editor of your choice, open the existing file to hold the code for your test program.
In this example, I named the test program ch15.cpp.
The next step (for wise programmers, and you know who you are) is to add a simple test driver to the source file so you can take a look at how all this plays out.
2. Type the code from Listing 15-4 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 15-4: THE TEST DRIVER FOR CONSTS
int main(int argc, char **argv)
{
Foo f;