Features


Object-Oriented Programming

Roger Stringer


Roger Stringer, president of Marietta Systems, specializes in consulting in C and C++ development under DOS and UNIX. He can be reached at (404) 565-1560.

This tutorial in object-oriented programming shows how to create a C++ class for record access to files. The files have fixed length records and can contain any value including '\0'. The class binaryfile can open and close these files, and read and write records. The class also supports record locking.

In this article, I'll demonstrate the following OOP capabilities:

The full source code is available from the C User Group shareware library.

The binaryfile Class

In C++ a class with no function definitions resembles a struct in C, except for the keywords private, protected and public. The concepts of C++ classes are clearly derived from the C structs, and the method to access the elements and functions of a C++ class are the same as for a C struct.

An object stores its characteristics in the data elements of the class, for example the name and record length. In the class binaryfile, all this information is private to the class, except for the pointer record used to point to the memory location of the current record.

Listing 1 shows the binaryfile class. The element mode indicates the access mode of the file, defined as enum FILEMODE (explained in Table 1) . The element share identifies whether the file is opened to allowed shared writing, reading or no shared access by other programs. handle is the system file handle number assigned to the file when open, and is set to -1 if no file is open. The element header is the size of the file header, to be skipped in reading records from the file. recnbr is the current record number, starting from 1L. locked is the number of the currently locked record if (<0L). If (>=0L) no record is currently locked. record is a pointer to the record area. record is a NULL pointer if unallocated.

In-Line Functions

Hiding information by making it private prevents unauthorized modification, but also prevents you from viewing the information. Listing 2 adds in-line functions to the class definition that provide read-only access to the private elements. For example, the in-line function getheader( ) retrieves the size of the header area. Most compilers treat in-line functions as macros with very little overhead. It is good programming practice to use this facility only for simple functions with minimal actual code.

Constructors And Destructors

A powerful aspect of C++ is the constructor and destructor. These functions handle initializing an object when it is created and performing cleanup when it is destroyed.

The constructor has the same name as the class. The destructor has the same name prefixed by a tilde (~) character. If you do not define them explicitly, the compiler will automatically define default functions.

Listing 3 includes all the function definitions in the class definition and the code for the constructor and destructor functions. Because these functions are defined outside the class definition, they must be prefixed by the class name and the scope access operator (::).

The constructor initializes the element handle to -1, and allocates the space for the record area, provided the maximum record size is less than 4096 bytes.

The destructor ensures the file has been properly closed before deallocating the space for the record area.

Opening And Closing The File

The function binaryfile::fileopen() in Listing 4 opens a binary file, and binaryfile::fileclose(), naturally, closes it. The fileopen() function uses default parameters that allow the programmer to ignore less frequently used parameters. The file access mode defaults to Readonly, the size of the file's header area defaults to zero, and the file sharing mode defaults to allow only shared read access. The fileopen() function performs error checking, sets the access and sharing flags, opens the file, and (if successful) initializes the elements in the object.

The enum FILEMODE is defined to identify, in clear English, the access mode of the file. The five modes I have defined appear in Table 1. The private utility function countrecords() establishes and sets the length of the file in records.

The fileclose() function unlocks any locked record and closes the file.

Reading Records From The File

C++ allows you to define different functions in the same class that have the same name, but that take different types of parameters. This feature is called function overloading. C++ differentiates between the functions of the same name by investigating the type of the arguments passed when the function is called.

I've used function overloading to allow the function fileread() to read a file sequentially or by record number (see Listing 5) . The compiler deduces that a fileread( ) function call with a long parameter is a command to read a specific record. For example, the statement

x.fileread (45L)
reads record number 45 from binaryfile object x.

Alternatively, the compiler interprets a fileread( ) function call without a parameter or with an enum READMODE parameter to be a sequential read instruction. For example, the code

{
binaryfile x(81); // 81 byte record file object
int i;
x.fileopen (filename);
for (i = x.fileread (LastRecord)
     ; i>0 ;
     i = x.fileread
       (PreviousRecord)) {
    ... // code executed on each record
}
x.fileclose(); }
reads backwards through a file opened as binary object x.

Writing Records To File

When a file is opened in Append or Recreate modes, all writing occurs by adding a new record to the end of the file. In Update or AnyWrite modes, the programmer can rewrite the current record, overwrite any existing record in the file or (in AnyWrite mode only) add a new record to the file. Again, I've employed function overloading to create two versions of the filewrite( ) function — one without a parameter, and one with a specific record number for Update or AnyWrite mode access. See Listing 6.

The command

x.filewrite()
in Append or Recreate modes appends a new record to the file object x. The same command in Update or AnyWrite modes rewrites the current record.

The command

x.filewrite (k)
where k is a long, writes out to record k. If k is greater than the current record count and the access mode is AnyWrite, the record is appended to the file. For example, the code in Listing 7 reads sequentially through a fiLe and updates each record.

File Sharing And Record Locking

The examples in this article support file sharing and record locking using the functions and flags supplied by Borland's Turbo C++ for MS-DOS (Listing 8) . Zortech's locking() function is slightly different. UNIX implementations are again different. The record locking scheme used here is quite simplistic, locking only one record per file, and does not implement the "set" concept of locking a set of records for subsequent processing.

The OOP concept of encapsulation hides the complexities and intricacies of different environments and implementations from the user. If an implementation-specific or enhanced locking or file sharing scheme requires that different data be stored in the object, you can make the change within the private data without altering the way you use the class. This is data encapsulation. The programmer knows exactly which functions can access the private data, and any change to this data format has specific scope and manageable consequences.