- •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
Implementing Properties 137
The problem is, you can also set age to an invalid value. The restriction is only implemented by the “rule” that an age can’t be outside the valid range of 18 to 80. Our code does not enforce this rule, which could easily cause problems in calculations that rely on the rule being obeyed. An invalid assignment might look like this:
f.age = 10; // Invalid
be useful for values that the programmer needs access to, but cannot possibly modify, such as the total memory available in the system. Properties like these save time by making the code easier to read while still maintaining data integrity.
Implementing Properties
The ideal solution would be to allow people to directly set the age property in this class, but not allow them to set it to values outside the valid range. For example, if a user did so and then added this statement
f.age = 10;
the age would not be set and would retain its old value. This resistance to unauthorized change is the advantage of a property, instead of allowing the value to change no matter what input value is given. In addition, we can create read-only properties that can be read but not written to. C++ does not offer this capability directly, but it allows us to create such a thing ourselves. A read-only property would
Creating a simple class that implements properties for a specific type — in this case, integers — can illustrate this C++ capability. We can then customize the class to allow only specific types of integers, or integer values.
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 ch25.cpp, although you can use whatever you choose.
2. Type the code from Listing 25-1 into your file.
Or better yet, copy the code from the source file on this book’s companion Web site.
LISTING 25-1: PROPERTY CLASS
#include <stdio.h> #include <string>
class IntProperty
{
int temp; int &iValue; bool bWrite;
public:
void Init()
{
bWrite = false;
}
IntProperty(void) : iValue(temp)
{
Init();
}
(continued)
138 Technique 25: Implementing Properties
LISTING 25-1 (continued)
virtual void set(int i)
{
iValue = i;
}
virtual int get(void)
{
return iValue;
}
public: IntProperty(int& i)
: iValue(i)
{
Init();
}
IntProperty( int i, bool read, bool write ) : iValue(i)
{
Init();
}
IntProperty( const IntProperty& aCopy ) : iValue(aCopy.iValue)
{
Init();
}
virtual ~IntProperty()
{
}
// Accessors
int getValue( void )
{
return iValue;
}
bool getWrite(void)
{
return bWrite;
}
void setWrite(bool write)
{
bWrite=write;
}
// Operators
IntProperty& operator=( int i )
{
if( bWrite ) set(i);
1
2
Implementing Properties 139
else
printf(“Trying to assign to a read-only property\n”);
return *this;
}
// Cast to int operator int()
{
return get();
}
};
This class implements a property according to the C++ standards, and yet works as if it were a Java or C# property. We will be able to read and write to the data value without having to write extra code, but the data values will be validated for the range allowed. To use it, we need to embed it in a class that the end user will interact with. This class will expose the IntProperty object to the end user, but the instance of the IntProperty class within any other class will work with an internal variable of that class. The IntProperty class is really just a wrapper around a reference to a variable, but that variable will be outside the scope of the
IntProperty class.
Notice that the set ( 1) and get ( 2) meth- |
|
ods of the class are internal |
to the class itself, |
but are also declared as virtual. That means implementing a derived class that screens out certain data values would be trivial, as we will see in the AgeProperty class later in this technique.
To derive a class from our IntProperty class, we just have to override the get and set methods in the ways we want. To restrict the range of the integer value, for example, we modify the set method to only allow the values we permit. In addition, we must override the operator= method, because operator= is never inherited by a derived class. That’s because you could be setting only a portion of the object — which the language won’t let you do, so you have to override the operator as well. When you create a
derived class, the operator= would be called for the base class. This would set only the member variables in the base class, and would not set the ones in the derived class. Otherwise, the remainder of the class remains the same.
3. Add the code from Listing 25-2 to your source file.
We could simply create a new file to store this new class, but it is easier to just combine them for the purpose of this technique.
In this case, we are going to use the IntProperty in another class.
LISTING 25-2: EXTENDING THE INTPROPERTY CLASS
class AgeProperty : public IntProperty
{
private:
virtual void set(int i)
{
if ( i >= 18 && i <= 80 ) IntProperty::set(i);
}
public:
AgeProperty( int &var ) : IntProperty(var)
{
}
AgeProperty& operator=( int i )
{
IntProperty::operator=(i); return *this;
}
};
140 Technique 25: Implementing Properties
Now, in order to use the class, we need to embed the object as a public member of our encapsulating class, and provide it with a data member that it can access to set and get values. The property class is just a wrapper around a value. Because it contains a reference to a data value outside the class, it can directly modify data in another class. That means that any changes made to the reference in the IntProperty class will be immediately reflected in the underlying class-member variable.
To show how it all fits together, the next section adds a class that makes use of the AgeProperty class.
Testing the Property Class
After we have defined the Property class, we need to test it. The following steps show you how:
1. In the code editor of your choice, create a new file to hold the code for your test program.
In this example, I named the test program ch25.cpp.
2. Put the code from Listing 25-3 into your testdriver file.
LISTING 25-3: TESTING THE INTVALUE CLASS
class TestIntValue
{
private:
int myInt; public:
TestIntValue() : i(myInt)
{
myInt = 0;
}
void Print()
{
printf(“myInt = %d\n”, myInt );
}
public: AgeProperty i;
};
This class contains a single integer value, its only data member. The data member is associated with the Property class in the constructor for the class, so any changes to the member variable will be immediately reflected in the Property class and the TestIntValue class at the same time.
Because the data value is used by reference in the Property class, changing the property is the equivalent of changing the original data member directly. We are controlling how the data is changed, while allowing the compiler to generate the code that does the actual data manipulation.
Our class illustrates how the data values change and what they are assigned to at any given moment in time. We will use this class to show off how the property class works.
3. Add the code from Listing 25-4 to the end of your existing file.
LISTING 25-4: THE TEST DRIVER CODE
int main(int argc, char **argv)
{
TestIntValue tiv;
tiv.i |
= 23; |
|
3 |
printf(“Value = %d\n”, (int)tiv.i ); |
|
||
tiv.Print(); |
|
5 |
|
tiv.i.setWrite( true ); |
|
||
tiv.i |
= 23; |
|
|
printf(“Value = %d\n”, (int)tiv.i ); |
|
|
|
int x |
= tiv.i; |
|
|
tiv.Print(); |
|
|
|
printf(“X = %d\n”, x ); |
|
|
|
tiv.i |
= 99; |
|
|
printf(“Value = %d\n”, (int)tiv.i );
}
4. Save the source file in your code editor and close the editor application.
5. Compile the file with your favorite compiler on your favorite operating system.
If you have done everything properly, you should see the following output from the application:
Testing the Property Class 141
$ ./a.exe |
|
|
Trying to assign to a read-only property |
4 |
|
Value = 0 |
|
|
myInt = 0 |
6 |
|
Value = 23 |
|
|
myInt = 23 |
|
|
X = 23 |
|
|
Value = 23 |
|
|
The output above illustrates that our property class is working properly. The initial value of the integer is 0, as specified in the constructor. Because the class defaulted to read-only (setWrite was not yet called), an attempt to write to the variable ( 3) results in no change being made ( 4). After we set the write
flag to allow changes ( 5), we can then assign values to the variable and have it modified in the output ( 6).
Properties are an essential part of languages such as C# and Java, but are not yet a part of the C++ languages. If you get into the habit of thinking about them, however, you can save a lot of time in the long run — for one thing, you won’t have to relearn how to use data members for classes. Translating code to and from C++ from the newer languages will become an essential part of mixed language projects in the future, and making it easy to do that translation will save you a lot of time and effort.