David Brumbaugh is a project manager at Advanced Information Services, a systems integrator in Peoria, IL. He has been programming in C for over five years and in C++ for over a year. He can be reached by mail at 2807 N. Renwood Ave., Peoria, IL 61604.
Most C programmers have large investments in existing C libraries. If C++ is to fulfill the promise of reusable software, the C libraries of today cannot suddenly become obsolete. Usually, existing libraries can be given "OOPness" by encapsulating their functions in C++ classes. Furthermore, if those classes are well-designed, they can enhance an application's portability, readability, and maintainability.
C++ And C
For all practical purposes, C++ is a superset of C. C++ can be linked with code compiled by a C compiler if the functions are prototyped and enclosed in the linkage specifier:
extern "C" { }This declaration lets you use C libraries directly, without modification. Of course, you must have a compatible object file to begin with. You can link a Turbo C 2.0 library and a Turbo/Borland C+ + program, but you cannot use the declaration to link a Turbo/Borland C+ + program and a Microsoft C 5.1 library. (I know because I tried.) While this scheme doesn't exploit C+ +'s bent towards OOP, almost anything your old C libraries can do can be made to work in C++.
Terms
Cutting away the object-oriented jargon leaves little difference between a method and a function. Objects contain both data and functions. In OO jargon, these are attributes and methods. (For the purposes of this article, I use "method" to denote a class's member function and "function" to mean a traditional C function.) In many cases libraries already contain most of the code for the methods. To make the libraries OO, you simply need to wrap classes around the libraries.
Preparation
In many cases, it is not necessary to wrap libraries. A library that is not often used or has no common thread probably is not a candidate for wrapping. On the other hand, a method may need some functions found in a library that is not wrapped itself. In this case, simply enclose the C function in extern "C" { } and call it as necessary. Often you can use conditional compilation in the library's header file. (See Listing 1. The __cplusplus macro is Turbo/Borland C++ specific.)Before wrapping a class around a library, ask yourself what advantages you expect. Typically, these might include:
C++ has several keywords not found in C: asm, catch, class, delete, friend, inline, new, operator, private, protected, public, template, this and virtual[1]. If any of your library functions have these names, you will have problems. To work around this, remove the prototype of the offending function from the header file using conditional compilation. Write a new function, with a different name, in C that calls the library function (see Listing 2 for an example).
- Common Access. By overloading methods, it isn't necessary to create functions named replaceString, replaceInt, replaceReal. A replace method will suffice. Code becomes more readable and, in turn, more maintainable.more readable and, in turn, more maintainable.
- Reusable Code. Object-oriented code is more reusable because it encourages encapsulation. In traditional programming, programmers tend to reuse code by using an editor's "cut and paste" functions. While this works, the same code in different locations frequently requires slight changes in variable names, function names, or control structures. An object-oriented style enhances code reusability by localizing and formalizing the areas being changed.
- Localized Impact of Changes. Changes in understanding, requirements, and design prompt changes in program code. A typical problem arises when two areas of a program call the same function. If that function is subsequently modified to accommodate a change in one of the two areas, the other area may behave improperly. An object-oriented solution creates a descendant of the object in the first area, where the change is needed. All other code remains the same. Again, the change is local and formal.
- Portability of Applications. By isolating platform-specific libraries from your application in private and protected methods, you can change the implementation details of a specific class without touching the actual application code (this is a variation on number three). While preparing to wrap the library in a class, remember that in C++, all functions need prototypes. Most libraries come with a header file containing the prototypes. If your library doesn't come with such a header file, you must make one in order to use the functions in C++.
The final consideration for encapsulation concerns the availability of source code. If you have purchased the source code for a commercial library, you may want to avoid the overhead of wrapping, and simply use the cut and paste method to build classes from the original source.
Designing Classes Around Libraries
Since a class encapsulates both data and functions, you must identify data structures in the library that can become attributes of your class. As a general rule, data structures should be made protected variables. You will want to group common functions and data structures into a class.Once you have identified the common data structures, determine the class protocol. A class protocol is the list of methods used to send messages to objects of the class. In C++, the class protocol is the set of public methods and data items. Use generic names for the methods in your protocol. A database library may have several read functions: db_read_int(), db_read_str(), db_read_float(), etc. The protocol for this set of functions would be read(int &), read(char *), read(float &), etc. The bodies of these methods would contain calls to the proper library functions.
Avoid the temptation to give class methods library-specific names. Create general classes that permit inheritance in the future. For example, a generic database class could have descendants that call two completely different database libraries. Changing from one database to another would require no changes to the application.
Just because a function is in a library does not mean it must be represented in the class. In the example that follows, the Pfm_List class concerns itself with a single table in a database. It does not need database creation or administration functions.
Round out the class with internal methods. In C++, methods designated as private or protected are internal and are used by other methods. The library will generally dictate the internal methods, but don't allow it to dictate the overall class concept.
Pinnacle File Manager Tables
I have translated into C+ + an example that I wrote for an earlier CUJ article, "Object-Oriented Programming In C" (July 1990). I used the Pinnacle File Manager v3.5 by Vermont Database Corporation as the library to encapsulate. (If you wish to run the code in the examples, you can obtain a free sample disk that is limited to 100 records per table from Vermont Database Corporation by calling 802-253-4437.)I first needed to decide how to implement the list concept I presented in July 1990. Turbo C++ came with a Container class library, which met some of the needs I was trying to meet with the LIST_CLASS. Should I try to fit my code into their hierarchy or create my own hierarchy? I finally decided to use my own. This hierarchy is shown in Figure 1.
A D_List defines the operations you would normally perform on a list. The list will be ordered in some way, even if only in a physical order. The list will have a "top" and an "end," and will include the concept of a "current" member. The methods in Table 1 are common to all lists.
The Files
LISTCLAS.H (Listing 3) defines the abstract class D_List. LISTCLAS.CPP (Listing 4) defines the source code. Note the large number of purely virtual functions. The D_Array descendant of D_List is a list in memory. Since it uses no commercial libraries, it is outside the scope of this article. The next class in the hierarchy, Pfm_List, is a class specifically designed to use the protocol defined in D_List to access a Pinnacle File Manager database table. This is a case of making a library fit your design, rather than designing around a library.Because the scope of the class is limited to a single list (table), wrapping the Pinnacle File Manager functions does not take full advantage of the manager's advanced features. Since the class hierarchy was in place before I had PFM, I will add more features to the class when I need them, either directly or through inheritance. I may also choose to create a friend class to apply some of the other features.
PINCLASS.H (Listing 5) defines Pfm_List. Note the extern "C" { } around the inclusion of PINNACLE.H, informing the C++ compiler that I intend to use the C functions defined in pinnacle.h. The data structures used by Pinnacle, DB and DBTAB, are private, providing enforced data encapsulation. The other methods (functions) are defined as public. In addition to the required constructors and destructors, two additional methods, DB_Handle() and TableHandle(), interface with other classes and functions requiring database access.
PINCLASS.CPP (Listing 6) contains the source code for the class Pfm_List. Note that the DB_ functions are the same functions defined between extern "C" brackets. This is where we "wrap" the C+ + classes around the library.
The sample application is an imaginary payroll list. PAYLIST.H and PAYLIST.CPP (Listing 7 and Listing 8) define the class and code for the class specific to the application.
PTEST.CPP (Listing 9) tests the classes. It is simply a small program that runs through the major methods of the classes. Note that with the exception of the constructor, there is no database specific code. You can see that if the company decided to port the PC application elsewhere, all the changes would occur within the PayList class.
Other files include the script for creating the database (Listing 10) and the database itself (PAYROLL.DB), which is included on the code disk.
Conclusion
One of the major advantages of C++ is its compatibility with the vast number of existing C libraries. In addition to protecting your investment, the encapsulation of libraries can enhance maintainability, portability, and ease of use.
Bibliography
[1] Stevens, A1, Teach Yourself C++, MIS:Press, Portland Oregon, 1990.