7.3.4 Features of C++ for C Programmers

Since more users are likely to be familiar with C than C++, this section provides a very brief introduction to the essentials of C++.

The central feature of C++ is that it extends the struct construct of C into a full-fledged object oriented programming (OOP) language based around a class. Thus, a class or object has data members, like a struct in C, but it also has functions associated with it. These member functions or methods perform various functions associated with the data members, and together the whole thing ends up encapsulating a set of related tasks or operations together in a single entity.

The object-oriented notion is very intuitive for neural-network entities like units:

class Unit {    // this defines an object of type Unit
public:         // public means that any other object can access the following

  // these are the data members, they are stored on the object  
  float         net_input;      // this gets computed by the weights, etc.
  float         activation;     // this is computed by the function below
  float         target;         // this is set by the environment before hand
  float         error;          // this gets computed by the function below

  // these are the member functions, they can easily access the data members
  // associated with this object 
  virtual void  Compute_Activation()
  { activation = 1.0 / (1.0 + exp(net_input)); }
  virtual void  Compute_Error()
  { error = target - activation; error *= error; }
};

The member functions encapsulate some of the functionality of the unit by allowing the unit to update itself by calling its various member functions. This is convenient because different types of units might have different definitions of these functions, but they all would have a Compute_Activation() function that would perform this same basic operation. Thus, if you have an object which is an instance or token of the type Unit, you can set its data members and call its member functions as follows:

Unit un;
un.net_input = 2.5;
un.target = 1.0;
un.Compute_Activation();
un.Compute_Error();

Thus, you use the same basic notation to access data members as member functions (i.e., the obj.mbr syntax). To illustrate why this encapsulation of functions with objects is useful, we can imagine defining a new object type that is just like a unit but has a different activation function.

class TanhUnit : public Unit {          // we're going to inherit from a Unit
public:
  void          Compute_Activation()    { activation = tanh(net_input); }
};

This notation means that the new type, TanhUnit, is just like a Unit, except that it has a different version of the Compute_Activation() function. This is an example of inheritance, which is central to C++ and OOP in general. Thus, if we have something which we know to be a unit of some type (either a Unit or a TanhUnit, or maybe something else derived from a Unit), we can still call the Compute_Activation() function and expect it to do the right thing:

Unit* un;
un->Compute_Activation();

This is an example of a virtual member function, because the actual function called depends on what actual type of unit you have. This is why the original definition of the Compute_Activation() function has the virtual keyword in front of it. Virtual functions are an essential part of C++, as they make it possible to have many different definitions or "flavors" of a given operation. Since these differences are all encapsulated within a standard set of virtual functions, other objects do not need to have special-case code to deal with these differences. Thus, a very general purpose routine can be written which calls all of the Compute_Activation() functions on all of the units in a network, and this code will work regardless of what actual type of units are in the network, as long as they derive from the base type of Unit.

While there are a number of other features of C++, the PDP++ software mainly makes use of the basics just described. There are a couple of basic object types that are used in the software for doing file input/output, and for representing character-string values.

The C++ way of doing file input/output is via the stream concept. There is an object that represents the file, called a stream object. There are different flavors of stream objects depending on whether you are doing input (istream), output (ostream) or both (iostream). To actually open and close a file on a disk, there is a version of the iostream called an fstream that has functions allowing you to open and close files. To send stuff to or read stuff from a file, you use something like the "pipe" or i/o redirection concept from the standard Unix shells. The following example illustrates these concepts:

fstream fstrm;     // fstrm is a file stream, which can do input or output

// we are opening the file by calling a member function on the
// fstream object. the enumerated type ios::out means 'output'
// also available are ios::in, ios::app, etc.

fstrm.open("file.name", ios::out);  

// we "pipe" stuff to the fstrm with the << operator, which can deal
// with all different types of things (ints, floats, strings, etc.)

fstrm << "this is some text " << 10 << 3.1415 << "\n";

fstrm.close();     // again, the file is closed with a member fun

The mode in which the file is opened is specified by an enumerated type or an enum. This provides a way of giving a descriptive name to particular integer values, and it replaces the use of string-valued arguments like "r" and "w" that were used in the open() function of the standard C library. The base class of all the stream types is something called ios, and the enum for the different modes a file can be opened in are defined in that type, which is why they are scoped to the ios class by the ios:: syntax. The definition of this enum in ios is as follows:

  enum open_mode  { in=1, out=2, ate=4, app=010, trunc=020,
                        nocreate=040, noreplace=0100 };

which shows that each enum value defines a bit which can be combined with others to affect how the file is opened.

The following example illustrates how file input works with streams. One simply uses the >> operator instead of <<. Note that the fstream has to be opened in ios::in mode as well:

fstream fstrm;     // fstrm is a file stream, input or output

// we are opening the file by calling a member function on the
// fstream object. the enumerated type ios::out means 'output'
// also available are ios::in, ios::app, etc.

fstrm.open("file.name", ios::in);

// these variables will hold stuff that is sucked in from the stream
String words[4];
int number;
float pi;
// this assumes that the stream was written by the output example
// given previously
fstrm >> words[0] >> words[1] >> words[2] >> words[3] >> number >> pi;

fstrm.close();     // again, the file is closed with a member fun