3.8 Putting It All Together
A Money Bag
The following program combines many of the features from this chapter.
Listing 3.18 Enumerated types, const references
// money.C - enumerated types and const references #include <iostream.h> enum coins { penny = 1,nickel = 5,dime = 10,qtr = 25,half = 50 }; enum bills { dollar = 1,five = 5,ten = 10,twenty = 20,fifty = 50 }; const int max = 5; struct money { // money definition coins coin[max]; bills bill[max]; }; int main() { void count(const money &); static money bag = { // fill the bag with money { dime, qtr, nickel, penny, nickel }, { fifty, ten, five, dollar, dollar} }; count(bag); // count the money return 0; } void count(const money & loot) { int paper = 0, silver = 0; for (int i = 0; i < max; i++) { paper += loot.bill[i]; silver += loot.coin[i]; } cout << "You've got " << paper << " dollars "; cout << "and " << silver << " cents." << endl; } $ money You've got 67 dollars and 46 cents.
This program counts money. We use enums to create coins and bills before we define a money type. The static initialization statement inside main() fills the money bag with coins and bills. We count the money with function count(), whose signature is const money &. The reference lets us pass the money bag efficiently, and const makes sure count() doesn't embezzle any money. Inside count(), a for loop counts the number of coins and bills separately. A cout statement displays the correct amount.
An Optimized Block Move Function
Suppose an application needs to move data from one memory block to another one as fast as possible. We could write a function in assembly language, but let's see if we can implement a fast, portable, C++ function instead. Rather than move single bytes between memory locations, we use a technique that generates block move instructions with most C++ compilers. We can't guarantee that our function will always produce efficient code, but performance should improve with compilers that generate block move instructions from this technique.
Our bmove() function knows nothing about objects, so we restrict it to memory blocks containing only built-in types (characters, integers, doubles, structures, etc.). The bmove() function has three arguments: a destination pointer address, a source pointer address, and an integer count. The function generates as many block moves as possible, but if there are any bytes "left over," we transfer them one byte at a time. To implement block moves as we copy data, we encapsulate structure assignments within bmove() (see "Structure Copy and Assignment" on page 113).
Here's the implementation of bmove() that transfers data blocks.
Listing 3.19 Function bmove() block moves
void *bmove(void *destptr, const void *srcptr, size_t count) { const int bsize = 256; // tunable block size parameter typedef unsigned char byte; struct mblock { byte buf[bsize]; // block buffer }; byte *dest = static_cast<byte *>(destptr); const byte *src = static_cast<const byte *>(srcptr); while (count >= bsize) { // transfer blocks *reinterpret_cast<mblock *>(dest) = // block move *reinterpret_cast<const mblock *>(src); dest += bsize; // increment dest pointer src += bsize; // increment src pointer count -= bsize; // decrement count } while (count--) // transfer remaining bytes *dest++ = *src++; return destptr; // return pointer to data }
Void pointers in bmove()'s signature allow applications to call it with pointers to any built-in type. The const void * with srcptr prevents bmove() from modifying any data that srcptr points to. A typedef defines byte as an unsigned character, and the mblock structure implements a 256-byte buffer for the block moves. The static casts convert void pointers to byte pointers so that pointer arithmetic is possible with dest and src. Note that we must preserve const with src and srcptr to avoid compilation errors with static_cast.
The first while loop implements 256 (bsize) byte block transfers. The statement
*reinterpret_cast<mblock *>(dest) = // block move *reinterpret_cast<const mblock *>(src);
performs the block moves. The reinterpret casts are necessary to convert byte pointers to mblock pointers. When we apply the indirection operator (*) to these mblock pointers, the compiler performs structure assignment. The result of this operation moves 256 bytes in memory from src to dest. After the transfer, we increment the pointers by 256 and decrement count by the same amount. Again, note the use of const with mblock and src to avoid compilation errors with reinterpret_cast.
The function performs block move transfers as long as count is greater than or equal to bsize. The second while loop transfers the remaining bytes one at a time. The function returns a pointer to its source address (first argument).
Here is a sample program that calls bmove() for integer and character arrays.
Listing 3.20 - Integer and character block moves
// bmove.c - optimized block move function #include <iostream.h> #include <iomanip.h> #include <stdlib.h> #include <memory.h> int main(int argc, const char *argv[]) { if (argc != 2) { cerr << "Usage: " << argv[0] << " length" << endl; return 1; } int i, length = atoi(argv[1]); int *a = new int[length]; // destination array int *b = new int[length]; // source array for (i = 0; i < length; i++) // fill with integers b[i] = i; bmove(a, b, length * sizeof(int)); // block move integers for (i = 0; i < length; i++) // display moved data cout << setw(4) << a[i] << (((i+1) % 10) ? ' ' : '\n'); cout << endl; char *c = new char[length]; // destination array char *d = new char[length]; // source array memset(d, 'x', length); // fill with 'x' chars bmove(c, d, length); // block move chars for (i = 0; i < length; i++) // display moved data cout << setw(4) << c[i] << (((i+1) % 10) ? ' ' : '\n'); delete [] a; delete [] b; delete [] c; delete [] d; return 0; } $ bmove 1200 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 . . . . . . . . . . . . . . . . . . . . . . . . . . 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 x x x x x x x x x x . . . . . . . . . . . . . . . . . . . . . . . . . . x x x x x x x x x x
The program creates source and destination arrays with operator new and a length from the command line. We fill both source arrays with data before calling bmove() to transfer data. The program displays 10 data elements on each line after the transfers.
On machines with 16-bit integers, bmove() performs 9 block moves (2,304 bytes) and 96 single-byte transfers with a length of 1200 (2,400 bytes). Likewise, bmove() performs 4 block moves (1,024 bytes) and 76 single-byte transfers for the 1200-byte character arrays. This means bmove() transfers 96 percent of its integer data and 85 percent of its character data with block moves for a length of 1200.
NOTE
We call the bsize constant in bmove() a tunable parameter. The performance of bmove() may vary, based on machine word size and the type of CPU you are using. You may want to experiment with different bsize constants to determine how bmove() performs on your machine.