Stylistic Differences Between C and C++


These notes describe the stylistic differences between C and C++. Remember from the recent lecture that the primary stylistic difference is that C is procedurally-oriented whereas C++ is object-oriented. In other words, in C the data structure is passed to a procedure, along with any data that the procedure needs in order to manipulate the data structure. By contrast, in C++, you list the name of the object first, then the method that should be called to manipulate the object, and then the data that should be passed to the method to aid in this manipulation. Also recall that in a C struct only data can be declared but that in a C++ class both data and methods can be declared. The following two programs help illustrate this distinction in styles.

Stack Manipulation

The first example is a program that pushes three integers onto a stack, then pops two of them off the stack and prints them.

C Version

The C version of the program uses Dr. Plank's stack library. The following is a slightly modified version of his stack.h file but it's similar in spirit to the one you saw in CS140 (we'll call it c_stack.h):
typedef void *Stack;

/* Allocate and deallocate a stack.  New_stack() calls malloc. */

extern Stack new_stack();
extern free_stack(Stack);

/* Test to see if the stack is empty -- or how many elements it as */

extern int stack_empty(Stack);
exter int stack_size(Stack);

/* Put something on the stack */

extern stack_push(Stack, Jval);

/* Take something off the stack */

extern Jval stack_pop(Stack);

/* Look at the top element, but don't take it off */

extern Jval stack_top(Stack);

Here is the C version of the program (c_numrev.c):

#include < stdio.h >
#include "c_stack.h"

main()
{
  Stack s;
  int i;

  s = new_stack();

  stack_push(s, new_jval_i(1));
  stack_push(s, new_jval_i(2));
  stack_push(s, new_jval_i(3));

  i = jval_i(stack_pop(s));
  printf("First pop: %d\n", i);

  i = jval_i(stack_pop(s));
  printf("Second pop: %d\n", i);

  free_stack(s);
}


C++ Version

The C++ version uses a stack object to implement the program. The class declaration for the stack is as follows (found in stack.h):


class Stack {
  public: 

    Stack();             // Create a stack
    ~Stack();            // Destroy a stack
    void push(Jval);     
    Jval pop();          
    bool isEmpty();
    Jval top();

  protected:

    class StackNode *top_node;
};

You may wonder what the Stack and ~Stack methods do. When the new operator is called, it calls an initializer method for the class. Stack is the initializer method. Similarly the delete operator calls a destructor method. ~Stack is the destructor method. C++ has the idiom that any method that has the same name as the class is an initializer method (called a constructor method in C++) and any method that has the same name as the class but is preceded by a tilde (~) is a destructor method. In this case, the initializer method performs the same tasks as new_stack and the destructor method performs the same tasks as free_stack.

Also notice that the declaration for the Stack class omits any variable declarations. The reason is twofold:

  1. The variables are part of the implementation: In general, a class's variables are not made available to the program. If the class wants to provide access to the variables, it does so by providing methods that return the variables' values. In this case, the stack does not wish to make any of its variables' values available to the program so it does not declare any methods that will return these values.

  2. The lack of variables drives home the point that you do not need to know how a class is implemented in order to use that class. All you need to know are the methods that the class provides and what each of these methods does.

Now that the preliminaries are over, here is the C++ version of the program:

#include "stack.h"
#include < stdio.h >

main()
{
  Stack *s;
  int i;

  s = new Stack;

  s->push(new_jval_i(1));
  s->push(new_jval_i(2));
  s->push(new_jval_i(3));

  i = jval_i(s->pop());
  printf("First pop: %d\n", i);

  i = jval_i(s->pop());
  printf("Second pop: %d\n", i);

  delete s;
}


Differences between the C and C++ Programs

There are three important differences between the C and the C++ programs that you should take note of:

  1. The C version passes the data structure to a function whereas the C++ version lists the object, then the method, and then any arguments to the method. For example, compare the C and C++ calls for pushing an element onto the stack:
    	C                          C++
    	stack_push(s, 1)           s->push(1)
    	

  2. The C version calls new_stack and free_stack to acquire a stack struct and to release a stack struct. The C++ version invokes the new and delete operators to perform the same task. In C++, new and delete are reserved keywords. new allocates an object and delete destroys an object. new and delete replace C's malloc function (which allocates memory for an object) and C's free function (which de-allocates memory for an object. The format of the new operator is:
    	new classname(arguments)
    	
    new allocates an object that has enough space to store the class's instance variables. It then calls the class's initializer method and passes it the arguments.

    The format of the delete operator is:

    	delete objectname
    	
    delete first calls the destructor method associated with the object's class, then de-allocates the object's memory.

  3. The C version has to prefix its function names with the prefix stack_, whereas the C++ version provides simple, concise names for its methods. The reason for the difference is that C++ does a better job of carving up the namespace so that name conflicts do not occur. The namespace of a program is the set of all global variable names, type names (i.e., struct, class, and typedef names) and function names declared in the program. In C all functions go into the same namespace so it is important ensure that function names that are likely to be popular (like push, pop, and top) are not multiply defined. The way to do this is to prefix function names that are associated with data structures with the name of the data structures. Hence we name push stack_push because it is associated with a stack. A side-benefit of this naming convention is that you can immediately tell whether or not a function is associated with a data structure, and if so, with which data structure it is associated.

    C++ has a more elegant solution however. Functions can either go into the global namespace or into the namespace of a class. Remember that functions that are not associated with a class are called functions (these functions go into the global namespace), whereas functions that are associated with a class are called methods (these functions go into their class's namespaces). Functions that belong to different namespaces and which have the same name do not conflict with one another. For example, a program could have a push method in the global namespace, a push method associated with a stack class, and a push method associated with a queue class. Hence the program has three different definitions of push, but it is always clear which push function is being used from the context in which push is being called. For example, the call push(x) is a call to the global function because this push is not associated with an object. The call s->push(x) where s is a stack object is a call to a stack's method because the push call is associated with the stack object s.

    Although the issue of carving up the namespace may not seem significant, it would if you were working on a real world system with hundreds of thousands or millions of lines of code. In such a system it can be very difficult to come up with distinct function names or to know whether a function name you've come up with has already been used. However, a class typically has at most a few dozen methods (often fewer) so it is much easier to know whether your method name is unique or not.


Printwords

The second example that illustrates the stylistic differences between C and C++ is a program that reads the lines of a file and prints out each word on a separate line, preceded by its line number. For example, given the input:

Chocolate is 
nice but ice
cream is dandy
the program should produce the output:
1: Chocolate
1: is
2: nice
2: but
2: ice
3: cream
3: is
3: dandy

C Version of Printwords

We are going to use Dr. Plank's fields library to read lines from the file. To refresh your memory, here is the fields.h file:

define MAXLEN 1001
#define MAXFIELDS 1000

typedef struct inputstruct {
  char *name;               /* File name */
  FILE *f;                  /* File descriptor */
  int line;                 /* Line number */
  char text1[MAXLEN];       /* The line */
  char text2[MAXLEN];       /* Working -- contains fields */
  int NF;                   /* Number of fields */
  char *fields[MAXFIELDS];  /* Pointers to fields */
  int file;                 /* 1 for file, 0 for popen */
} *IS; 

extern IS new_inputstruct(IS);
extern int get_line(IS); /* returns NF, or -1 on EOF.  Does not
                                  close the file */
extern void jettison_inputstruct(IS);  /* frees the IS and fcloses 
                                                the file */

Using the fields library, here is the C Version of printwords (c_printwords.c):

#include < stdio.h >
#include "fields.h"

main(int argc, char **argv)
{
  IS is;
  int i;

  if (argc != 2) {
    fprintf(stderr, "usage: printwords filename\n");
    exit(1);
  }
 
  is = new_inputstruct(argv[1]);

  // right here we should check whether is is null and
  // report an error if it is but the C++ code handles
  // this case slightly differently (and in a way that
  // is irrelevant here) so the check is omitted

  while(get_line(is) >= 0) {
    for (i = 0; i < is->NF; i++) {
      printf("%d: %s\n", is->line, is->fields[i]);
    }
  }

  jettison_inputstruct(is);
  exit(0);
}

C++ Version of Printwords

The C++ version uses an object-oriented version of Dr. Plank's fields library. In particular, we declare a Fields object that contains both the data of an inputstruct and the functions provided by the fields library. However, in true object-oriented fashion, the functions and the data are packaged together in an object and the functions are called methods. Also, at this point I do not want to get too wound up in precise C++ syntax so the following C++ declaration for the Fields class is only approximate:

class Fields {
 public:
  Fields();                       // implements new_inputstruct for stdin
  Fields(const string filename);  // implements new_inputstruct for a file
  ~Fields();                // implements jettison_inputstruct
  int get_line();	      // implements get_line
  int get_line_number();    // return the current line number
  int get_NF();             // return the number of fields in the 
  string get_name();        // return the name of the file
  // current line
  string get_current_line();   // returns the current line
  string get_field(int i); // return the ith field in the current line
protected:
  IS file;
  string name;
};

Note that the Fields and ~Fields methods are initializer and destructor methods respectively and that they do the work of new_inputstruct and jettison_inputstruct.

Also, like the Stack class declaration, the declaration for the Fields class omits any variable declarations. The reason is twofold:

  1. The variables are part of the implementation: In general, a class's variables are not made available to the program. If the class wants to provide access to the variables, it does so by providing methods that return the variables' values. In this case, the get_line_number, get_NF, and get_field methods return the values of the line, NF, and field variables in the fields inputstruct.

  2. The lack of variables drives home the point that you do not need to know how a class is implemented in order to use that class. All you need to know are the methods that the class provides and what each of these methods does.

Now that the preliminaries are finished, here is the C++ version of printwords (printwords.cpp):

#include < stdio.h >
#include "fields.h"

main(int argc, char **argv)
{
  Fields *is;
  int i;

  if (argc != 2) {
    fprintf(stderr, "usage: printwords filename\n");
    exit(1);
  }
 
  // get a new Fields object 
  is = new Fields(argv[1]);

  // print out the words prefixed by a line number
  while(is->get_line() >= 0) {
    for (i = 0; i < is->get_NF(); i++) {
      printf("%d: %s\n", is->get_line_number(), is->get_field(i).c_str());
    }
  }

  // destroy the Fields object
  delete is;
  exit(0);
}

Differences between the C and C++ Programs

There are four important differences between the C and the C++ programs that you should take note of:

  1. The C version passes the data structure to a function whereas the C++ version lists the object, then the method, and then any arguments to the method. For example, compare the C and C++ calls for get_line (get_next_line is the same as get_line):
    	C                          C++
    	get_line(is)               is->get_line()
    	

  2. The C version calls new_inputstruct and jettison_inputstruct to acquire an inputstruct and to release an inputstruct. The C++ version invokes the new and delete operators to perform the same task. To repeat what was said in the previous example, C++ uses new to allocate an object and delete to destroy an object. new and delete replace C's malloc function (which allocates memory for an object) and C's free function (which de-allocates memory for an object.

  3. The C version allows the programmer to directly access the variables in an inputstruct whereas the C++ version requires the programmer to call methods to gain access to the variables' values. Hiding the variables is called "information hiding" and we will discuss it in greater detail as the class progresses. For the time being it suffices to say that by hiding the variables from the programmer, we hide the implementation from the programmer, which makes it easier to change the class's implementation without affecting the programs in which the class is used.

  4. The printf in the C++ version has the following method call:
    	is->get_field(i)->c_str()
    	
    This call is parsed as follows: (is->get_field(i))->c_str(). In other words, is's get_field method is called first. If you look at the class declaration for Fields, you will see that get_field returns a string. So is->get_field(i) returns a string object. This string object's c_str method is then called. c_str returns a char *. The reason the string's c_str method must be called is that printf knows how to print char *'s but it does not know how to print strings. The sequence of two method calls should help reinforce your notion of C++ as being object-oriented. The object is gets passed the called to get_field. This call results in a string object being returned. In turn, this string object gets passed the call to c_str, which results in a char * being returned.