C Language


Now let's turn to the darker side of C++. Suppose, in the spirit of OOP, you decide to "advance" from standard C programming techniques to C++ techniques. One of the first ways you might try this new approach is by putting an object "wrapper" around calls to system functions. The example I use (based on an example in C++ Programming Style, by Tom Cargill) assumes there are system functions to iterate over a list of output files in a specified output queue. This example uses an OUTQ type that is simply a system "handle" for an output queue control block allocated by the system when open_outq is called. The example also uses the following OUTQ_ENTRY structure, which describes the layout of a static area filled by a call to the system-supplied next_outq_entry function:

 struct OUTQ_ENTRY {
  char ofile_name[NAMESIZE];
  // ... Rest of OUTQ_ENTRY fields

Figure 8.1 shows a fragment of the OutQ class definition and the nextname member function. The following code shows how you might declare two OutQ objects and then attempt to print the first entry from both, side-by-side:

 OutQ q1("PRINTER1");
 OutQ q2("PRINTER2");
 printf("%s\t%s\n", q1.nextname(),q2.nextname());

If the first entry in q1 was "LISTINGA," and the first entry in q2 was "LISTINGB," you might expect to print


But what actually would print is


The problem arises because the system's next_outq_entry function returns a pointer to a static area that is overwritten by the most recent call. Another way to look at it is that, even though this example uses C++'s class facility to declare separate q1 and q2 objects, these objects implicitly share common storage via the next_outq_entry function.

Figure 8.2 addresses this problem by providing an OutQ member to hold the most recent output file name for each output queue. Although the code in the previous example will now work, the following code still fails to do what you might expect:

 OutQ q1("PRINTER1");
 printf("%s\t%s\n", q1.nextname(),q1.nextname());

Instead of printing the first two output files in q1, this code prints the second output file twice. Although the HoldName member avoids memory conflicts between separate objects, it does not avoid memory conflicts between multiple invocations of the same object's state-changing member functions. The solution to this problem is much more complex, requiring either some form of dynamic memory management by the OutQ class, or special coding techniques for multiple references to the same OutQ object. As this example illustrates, object-oriented programming in C++ is no simple panacea to all your programming ills. It's often the case that half-complete class definitions introduce sneaky traps for the unwary. And creating robust classes for what appear at first to be simple objects is often much more difficult than you first imagine.

This example suggests a guideline that applies to both the implementation and use of C++ objects: Understand the lifetime of object data.