Appendix C. Introduction to C++

This chapter introduces the basic concepts of programming in C++. It briefly covers the principal concepts that differentiate C++ from non-object-oriented languages. Rather than providing a definitive overview, it gives C programmers a basic grasp of the C++ concepts and phrases that are occasionally used in this guide. If it has the side benefit of piquing the interest of C programmers enough to give C++ a try, so much the better. One primary benefit of programming in C++ is that you can extend the IL as you wish, for example, to include support for your image file format or for an image processing algorithm.

Objects and Classes

If you know that C++ is an object-oriented language, you correctly assume that objects play a major role in a C++ program. An object is an instance of a C++ class. As a C programmer, you are familiar with structures which provide a convenient grouping of variables. A class is a fancy data type that defines not only data elements, as in a data structure, but functions that manipulate those data elements. These data elements are called the class's data members, since they belong to the class; similarly, the functions that manipulate the data members are called member functions.

One key member function is the constructor, which contains instructions about how to create a class object. Typically, the constructor initializes the values of the data members. The class destructor deallocates the class object. In C++, you can have the compiler automatically create objects for you:

goodClass myGoodClass(anArg);

This statement defines the variable myGoodClass as being an instance of the class goodClass; it invokes the goodClass constructor to create myGoodClass, passing in the variable anArg as an argument to the constructor. Since storage is allocated for myGoodClass, you can now invoke any of its member functions:

myGoodClass.doItNow(someArg, anotherArg);

This statement invokes the doItNow() member function, explicitly passing in two arguments and implicitly passing the data elements of myGoodClass. Note the use of the dot operator (“.”) to access the doItNow() member function of the goodClass. You can also use this operator to access a data member of a class object, for example,

int defaultValue = myGoodClass.goodDefault;

where goodDefault is defined in the goodClass class.

Since the myGoodClass object is created automatically, it is also deleted (its storage freed) automatically, that is, the class destructor is called automatically when the function goes out of scope.

You can explicitly create an object as shown below:

goodClass* myGoodClassP = new goodClass(anArg);

Here, the goodClass constructor is explicitly called with anArg as the argument; note that the constructor has the same name as the class and that it returns a pointer to the class object. So, instead of a class object, you now have a pointer to a class. In this case, to access one of its members, you have to use the arrow operator (“->”):

myGoodClass->doItNow(someArg, anotherArg);

Since you have explicitly created the myGoodClassP object, it is not automatically deleted. You have to do this yourself:

delete myGoodClassP;

This statement calls the goodClass destructor to delete the object.

Overloaded Functions

A function in C can only be declared once. In C++, however, it is permissible to provide more than one declaration of a function as long as the arguments in each function are different. Since the function has more than one declaration, it is called overloaded.

Overloaded functions are used most commonly to declare class constructors. For example, you might have the following constructors:

myClass();
myClass(int arg1, float arg2);
myClass(myType type);

The arguments that you pass into the constructor determine which version of the constructor is used. You cannot, however, make the following declaration because the arguments have the same form:

myClass(int serialNumber, float accuracy);
myClass(int imageNumber, float resolution);

Inheritance

Classes can inherit data members and member functions from other classes. Inherited members are available for use by a class just as though they were defined in the class itself. Inheritance occurs when one class is derived from another. The derived class inherits the member functions and data from its parent, unless those members are marked as private. (“Public versus Protected versus Private” describes the meaning of “private.”) Thus, classes exist in an inheritance hierarchy. As shown in the inheritance hierarchy in Figure C-1, bestClass inherits from betterClass, which itself inherits from goodClass.

Figure C-1. Sample Inheritance Hierarchy

Figure C-1 Sample Inheritance Hierarchy

In this example, betterClass is “better” since it inherits members from goodClass and also defines its own; similarly, bestClass inherits members from goodClass and betterClass, and it defines its own. The root of a hierarchy is called the base class—in this example, the base class is goodClass. Typically, the base class has several subclasses that derive from it; it defines general capabilities common to every class in the hierarchy. A subclass then adds definitions of whatever members it needs to implement in order to provide its specific functionality.

A superclass can declare a member function as virtual, giving a subclass the opportunity to provide its own definition of that function. In some cases, virtual functions are simply declared but not implemented at all in a superclass. These are called pure virtual functions, and they must be overridden by a subclass's own version. You cannot create an object of a class that contains pure virtual functions; such a class is called an abstract class.

Public versus Protected versus Private

A class cannot use all of its superclass's members. Some of a class's members are declared private, and they are available for use only by the member functions of that class. Other members are declared protected, and these are available for use by derived classes. Yet other members are declared public, and they are accessible anywhere in the program.

Passing by Reference

The C++ language allows variables to be passed by reference (as Fortran does). For example, here is the declaration of a query function getAttribute(), which returns an attribute's value by reference:

void getAttribute(int& val);

Here is how you use this function:

int x;
myGoodClass.getAttribute(x);

It looks like getAttribute() is taking the variable itself, but behind the scenes, C++ actually passes a pointer to x.

Default Values

Another handy thing C++ allows you to do is to specify default values for a function's arguments. You do this when you declare the function:

void thisFunction(int arg1, int arg2 = 5);

Subsequently, you can call thisFunction() without explicitly specifying the second argument:

myGoodClass.thisFunction(3);

This statement invokes the function, passing in 3 as the first argument and 5 as the second. Additionally, you can specify whatever value you wish for the second argument instead of relying on the default, as shown below:

myGoodClass.thisFunction(3, 7);

Class Declaration Format

Example C-1 is a skeletal example of a class declaration to give you an idea of the declaration format.

Example C-1. Class Declaration Format


#include <il/ilLink.h>
#include <il/ilImage.h>
class ilViewTile : ParentClass {

public:

    float red, green, blue;

    ilViewTile() 
       { tile.x = tile.y = tile.nx = tile.ny = 0; mode = 0; }
    ilViewTile(const iflTile2D<int>& t, int m)
       { init(t, m); }

protected:

    void qRender(ilMpNode* parent, 
        const iflTile2D<int>& tile, int mode);

private:

    void init(int mode);
    void initSize(int mode);
};

In Example 1-1, ilViewTile class derives from ParentClass. The constructor for the class, ilViewTile() is overloaded: it has two forms. The constructors are public functions.

The function qRender() is protected. The init() and initSize() functions are used internally in the class and so are marked private.

Linking with Libraries in Other Languages

If you program in C++, you probably want to link with object files and libraries written in languages other than C++, especially C. In order to do so, you must include in your program declarations for the functions you wish to call. In most cases, you can do this by including appropriate header files with the #include directive. For the standard C header files supplied by Silicon Graphics, using #include is all you need to do. For example, if you are going to use C standard I/O and the Graphics Library, write:

#include <stdio.h>
#include <GL/gl.h>

If you want to call C functions from within a C++ program, either directly or by file inclusion, make sure that the C++ program contains correctly prototyped declarations for the functions. Also, the function declarations need to be recognizable by the C++ translator as declaring functions whose definitions are in C.

These steps are necessary because C++ normally encodes function names to support overloading. For example, the real name of a function declared in a C++ program as:

 void printf(char*, ...)           is         printf__FPce. 

The printf() function in libc.so, however, is called printf. To allow a C++ program to call functions written in C, C++ provides linkage specifications. To use the standard printf() function, for example, write:

extern "C" {
   void printf(char *, ...);
}

within the C++ source file that calls printf(), or within a header file that is included by the source file. The extern C statement tells the translator that the function linkage should be done according to the conventions used by the C programming language.

If you want to adapt an existing C header file or create a header file of your own containing C function declarations, and you want to be able to include it in either C or C++ programs, you can use the symbol __cplusplus (with two underscores preceding it). __cplusplus is always defined for C++ compilations and is otherwise undefined. Thus, you can enclose C function declarations with:

#ifdef __cplusplus
extern "C" {
#endif

and

#ifdef __cplusplus
}
#endif

This scheme is used to create the C and Fortran interfaces to the IL.

Referring to Function Names

The most important thing you need to know when debugging C++ programs with dbx is how to refer to functions and data members:

  • Member functions. Refer to these as classname::functionName. For example, to set a breakpoint in class C's member function f(), type:

    stop in C::f 
    

    If there is more than one member function named f(), this command will set a breakpoint in every such function. (However, you cannot set a breakpoint in an in-line function.)

  • Global C++ functions. Refer to these as ::functionname. For example, to set a breakpoint in the global function f(), type:

    stop in ::f 
    

  • Non-C++ functions. Refer to these as functionname. For example, to set a breakpoint in printf(), type:

    stop in printf
    

  • Data members. You cannot refer to a data member by its name alone, even if the program is stopped in a member function. To refer to data member m, use this–>m.

The following example illustrates various possibilities:

#include <stdio.h> 
class foo {
int n; 
public:
foo() {n = 0;}						// this is an inline function 
foo(int x);
int bar();
int bar(int); 
};
int foo:: bar() 
{
return n; 
}
int foo:: bar(int x) 
{
return n + x; 
}
foo::foo(int x) 
{
n = x; 
}
int square(int x) 							// this is a global function
{
return x * x; 
}
main() 
{ 
foo a; 
foo b = 11; 
int x = a.bar(); 
int y = b.bar(x) + square(x);		
printf("y = %d\n", y); 
} 

If you type:

stop in foo::foo 

execution will stop in the constructor for the variable b but not in the constructor for the variable a because you cannot set a breakpoint by name in an in-line function.

If you type:

stop in foo::bar 

execution will stop both when a.bar is called and when b.bar is called because the debugger is unable to distinguish between the overloaded functions.

To stop in square, type:

stop in ::square 

To stop in printf (a C function), type:

stop in printf