Appendix A. An Introduction to Object-Oriented Programming for C Programmers

Open Inventor is an object-oriented toolkit for developing 3D programs. It is written in C++, but it includes a C programming interface. This book is full of references to classes, subclasses, and other concepts from object-oriented programming. All examples are in C++. You will get the most from this book if you have a reasonable understanding of classes and objected-oriented programming before you begin reading it.

This appendix provides an informal introduction to object-oriented programming for C programmers and an overview of the concepts behind the C interface. If you are comfortable with object-oriented programming, you can skip the first section and just skim the example in this appendix. For the specifics of Inventor's C programming interface, see Appendix B.

This chapter contains the following sections:

What Is Object-Oriented Programming?

Many successful programmers use object-oriented techniques without knowing it. You have probably heard programmers complimenting an implementation by describing it as modular. Modular code has a well-defined interface that works without requiring its users to know how it was written. Modular code can be reused by many applications, cuts down on programmer learning time, and allows the implementation internals to change without affecting the programs that use it. It protects the programmer from the implementation details. The programming interface defines the functionality.

Data Abstraction

For an example, look at the file I/O functions provided in the standard C library: creat(), open(), read(), write(), and close(). These routines clearly define the I/O functionality without revealing the file system details or implementation. Each function uses a file descriptor to identify the data representing the file, device, or socket. The data structures that represent these objects are different for each file type, yet they are completely hidden from you as a programmer. The open/close/read/write semantics apply consistently to each object.

This technique of hiding internal data structures is known as data abstraction—the first fundamental concept of object-oriented programming. It's good programming practice to confine access to data structures to the code that is intended to modify the structures. Revealing private data allows the programmer using the structure to modify things that perhaps he shouldn't modify. The programmer is then relying on details of the internal implementation, so the implementor can't make changes to that internal representation.

Objects represent the building blocks from which programs are constructed. They consist of data structures and some associated functions that operate on those data structures. Objects perform functions on themselves rather than allowing applications to have access to their internal implementation. In our example, the C file I/O routines define a generic file (the object) that is accessed through the open/close/read/write functions.

Inheritance

So far, we've described good, modular code, but not specifically object-oriented programming. Inheritance is the concept that sets object-oriented code apart from well-written modular code. Inheritance allows you to create new objects from existing objects. It makes it easy to customize and specialize your programs. Inheritance is the second fundamental concept of object-oriented programming.

You've probably often wanted to reuse some existing code, but you couldn't because you needed to make minor changes. So you copied the code with the changes into an independent implementation. This reinvention is tedious, error-prone, and a waste of your time. Inheritance provides you with a mechanism for reusing your existing code and adding small changes, without starting over.

The C file I/O routine example actually defines three object types: files, devices, and sockets. These objects are created from the generic file object, which defines the open/close/read/write semantics. Writing the I/O routines is just a matter of implementing those functions for each type of file object. The implementation differences stay hidden from the programmer.

Implementing Data Abstraction and Inheritance: Classes

Object-oriented programming languages use the techniques we've described in a formal manner. C++ provides a few extra constructs on top of C that enforce these techniques. The most basic of these constructs is the class.You can think of a class either as a data structure with relevant functions attached, or as a group of related functions with some data attached. It doesn't matter which model you prefer. The important concept to understand is that objects encapsulate related data and functions into a single package, called a class.

Functions within a class are usually called member functions, or more generically, methods. The data structures within a class are referred to as member variables. So a class is composed of member functions and variables.

Note that we're using the term class to represent the abstract notion of an object, much like a structure in C. The term object usually refers to an instance of a class. You create an object from a given class when you instantiate the class. The C parallel would be allocating memory to make a copy of a structure. You can refer to that copy of the structure as an instance of the structure, or as an object with the same type as the structure.

When new classes are defined, they can be derived from an existing class. The existing class is called a base or parent class, and the new class is called the derived class or subclass. New classes created this way typically inherit all of the methods and variables that were defined in the base class.

Class Hierarchies

Open Inventor is composed of a large set of related classes that implement many aspects of 3D programming. These classes are implemented in C++. The Open Inventor C programming interface allows you to use these classes from C programs. So you can write C programs that reap the benefits of C++ inheritance without needing to learn C++ first. But the C interface does not hide the fact that there are classes. Your programming tasks will be easier if you understand the Inventor classes and how they relate to each other. For example, you need to know which class each class is derived from to know which functions apply.

Class relationships in object-oriented systems are often illustrated through class hierarchy diagrams, or class trees. Figure A-1 is an example. It illustrates a fictitious class hierarchy. Note that this example is not based on Inventor. It is used to convey key concepts in a simple manner, but its sphere, cone and quad mesh are for example only and are not the same as the Inventor classes with similar names.

Figure A-1. Sample Class Hierarchy Diagram


Functions and variables defined in the class Geometry also exist for every subclass. So if Geometry has a variable Bbox and a function getBbox(), all the subclasses of Geometry also have Bbox and getBbox().

See Chapter 1 for a summary of the Open Inventor class tree.

An Example of a Class: Sphere

This section discusses an example of a C++ class and its member functions. The class we'll consider is one from the fictional class tree shown in Figure A-1: Sphere, which represents and operates on a sphere. The Sphere class is defined below with several member functions and some member variables:

class Sphere {
public:
	   Sphere();				         // creates a sphere with default values
	   ~Sphere();				        // destructor, which deletes a sphere
	   void render()    				 // renders the sphere
	   Boolean pick(int x, int y); // picks the sphere

	   float radius;  					   // radius of the sphere
	   float center[3];  					// center of the sphere
};

Sphere is a class that creates, manages, and operates on a geometric sphere object. The internal implementation details of the sphere are not exposed to you.

Notice that the functions of Sphere do not have a sphere argument. When
you invoke these functions from C++, you invoke them from the class itself. Each function has an implied sphere argument. For example, this is how you would create a sphere, set its radius, and then render it in C++:

Sphere *mySphere;							        // pointer to a sphere object
mySphere = new Sphere();							 // creates and initializes sphere
mySphere->radius = 3.0;							  // sets the radius
mySphere->render();							      // renders it

The sphere-> syntax accesses a member variable or invokes a member function in the same way that C accesses structure members. For example, mySphere->render() invokes the render() function on mySphere. The new Sphere() syntax creates a sphere, allocating memory for the object and initializing it.

This is how the sphere class would look in the corresponding C interface:

Sphere *SphereCreate();
void SphereDelete(Sphere *sphere);
void SphereRender(Sphere *sphere);
Boolean SpherePick(Sphere *sphere, int x, int y);

(This example follows the naming conventions for Inventor C functions. For details on those conventions, see Appendix B.)

The C interface would also define a structure for the sphere:

struct Sphere {
	   char   		pad[48];			          /* padding generated by Inventor */
	   float  		radius;
	   float		  center[3];
};

The pad[48] is generated automatically from the C++ code. These pad statements are a by-product of the generation of the C interface from the C++ classes. They protect private data that you as a programmer shouldn't need to access.

To create a sphere, set its radius, and render it from C, you would write code like this:

Sphere *mySphere;						      /* my sphere object */

mySphere = SphereCreate();
mySphere->radius = 3.0;
SphereRender(mySphere);

Notice how similar this code is to the C++ example. The main difference is syntax. (Again, note that this is a hypothetical example; this is not exactly how the radius for a sphere is specified in Inventor programs.)

An Example of Inheritance: HollowSphere

Recall that inheritance is the ability to build specialized classes from existing classes. In C++, you can create subclasses of a class, which are identical to the parent class with exceptions you can select. These exceptions can be different implementations of functions on the parent class, or extra added functions. Subclasses are said to be derived from or subclassed from the parent class.

For example, we can build a subclass of Sphere called HollowSphere. HollowSphere is identical to Sphere, except that it has a thickness value and a new function that tells it whether to render translucently. HollowSphere is derived from Sphere. Since it's a subclass of Sphere, all member functions of Sphere also apply to HollowSphere. Our definition of HollowSphere does not have to define delete(), render(), or pick() functions. HollowSphere inherits these functions from the Sphere class. The same is true of Sphere's member variables, radius and center: HollowSphere inherits those as well.

Here is the C++ class definition for HollowSphere:

class HollowSphere : public Sphere {											        // subclass of Sphere 
	   void		   showEquator();					              // show equator during render
	   float  		thickness;					                // stores thickness value
}

The following C++ code fragment creates a hollow sphere, sets its radius and thickness, turns on the equator options, and renders it:

HollowSphere *mySphere;
mySphere = new HollowSphere();
mySphere->radius = 3.0;
mySphere->thickness = 0.25;
mySphere->showEquator();
mySphere->render();

To do the same using the C interface:

HollowSphere *mySphere;
mySphere = HollowSphereCreate();
mySphere->radius = 3.0;
mySphere->thickness = 0.25;
HollowSphereShowEquator(mySphere);
HollowSphereRender(mySphere);  /* inherited from parent class */

Note that when you invoke a method from the parent class, the method name is prefixed by the name of the subclass. See Appendix B for a fuller explanation of how the Inventor C interface names inheritance methods.

Suggested Reading

If you want to learn more about C++ and object-oriented programming, the following books are good starting points:

  • Dewhurst, Stephen C., and Kathy T. Stark, Programming in C++. Englewood Cliffs, N.J.: Prentice-Hall, Inc., 1989.

  • Ellis, Margaret A., and Bjarne Stroustrup, The Annotated C++ Reference Manual. Reading, Mass.: Addison-Wesley, 1990.

  • Lippman, Stanley B., A C++ Primer, 2e. Reading, Mass.: Addison-
    Wesley, 1991.

  • Pohl, Ira, C++ for C Programmers. Redwood City, Ca.: The Benjamin/
    Cummings Publishing Co., 1989.

  • Shapiro, Jonathan, A C++ Toolkit. Englewood Cliffs, N.J.: Prentice-Hall, Inc., 1991.

  • Weiskamp, Keith, and Bryan Flamig, The Complete C++ Primer, 2e. San Diego, Ca.: Academic Press, 1992.