Notes N15

Math 10 -- D. C. Smolarski, S.J.
Santa Clara University, Department of Mathematics and Computer Science

[Return to Math 10 Homepage | Return to Notes Listing Page]

Contents


More on C++ Classes

C++ classes (or structures) are record variables which can include subsections ("fields") of different data types (unlike arrays).

Besides containing data, C++ classes (and structures) may also include functions specific to the class (or structure), a practice which can aid in the process of data encapsulation. Such functions are often called member functions or methods. Data encapsulation provides protection against unforeseen access of data, providing the opportunity for limiting access by means of special functions which are part of the class itself.

As a general principle, if member functions are included along with data records in a class, the member functions usually all have attribute public and the data themselves have attribute private. The data, with attribute private, may be accessed by any of the member functions of the class, but may not be accessed directly by the main() program or any other function.

It is also possible to encapsulate a single data type with certain member functions or methods, to insure that the data will be accessed or changed only in specific ways. For example, a database (i.e., array) of names could be included as part of a C++ class along with functions to read in the names, to sort the names, and to print out the names. If this were done, then one could be assured that the array of names would never be touched by mistake. The only way one could access the information would be through the member functions, which are also part of the same C++ class.

The following is an example of a simple class named Circle with one private data element ("radius") and three member functions ("Initialize," "Circumference," and "Area").

class Circle
{
    private:
        float  radius;
    public:
        void   Initialize(float input_radius);
	float  Circumference(void);
	float  Area(void);
};
By looking at this class definition, we see that it consists of four components, but, as written, we don't really know what the public members do, since their definitions are not included in this "blueprint" of the class. We still need to define the three member functions. It is common practice (and some would say, preferred practice), to define the member functions outside of the class definition and thus use these references to the functions as kinds of "prototypes" (cf. Notes N8 for overview or review).

To define the member functions outside the class definition, we indicate the name of the class and then use the scoping operator :: along with the name of the function being defined. For example, the following code is one way of defining the three "member functions" indicated above.

     void Circle::Initialize(float input_radius)
     {    radius = input_radius;
     }
     float Circle::Circumference(void)
     {    return 2*3.1415926*radius;
     }
     float Circle::Area(void)
     {    return 3.1415926*radius*radius;
     }
Because these function definitions are actually considered part of the class Circle, the variables declared in the class are accessible to each of the functions without further declaration. Thus, even though each function refers to radius, it is not declared internally since it has been declared in the class Circle. This is the only case in which a variable does not have to be declared locally in a function.

The combination of the class definition above and the member function definitions would enable a programmer to declare variables of this Circle data type and make use of the component parts. For example,

int main()
{ Circle pool;
     pool.Initialize(4);
     cout << "circum = " << pool.Circumference() << endl;
     cout << "area = " << pool.Area() << endl;
     return 0;
}
In this code, pool is a new variable declared to be of type Circle defined above. We can access the component member functions using the dot operator (as with any component of a class or struct variable in C/C++).

The main program first initializes the radius of pool to a value of 4. It then computes the value of the circumference and area by calling the appropriate member function component of the variable pool.

NOTE that since the component data cell radius has been given the attribute of private, it cannot be accessed directly from the main program (or any other function). If we tried to access radius, e.g., via a statement such as

     cout << pool.radius << endl;
we would get an error at compile time.

Alternative Location for Function Code

One may include the body of any member function code within the class definition. As a matter of style, some authors prefer the indication of such code separately, however. For shorter programs, it is probably easier to include the member function code within the class declaration, while with longer code, it is probably better merely to indicate the "prototypes" within the class, and place the body of the code elsewhere.

As an example of how to combine all code together, the following is a rewriting of the code given above.

class Circle
{
    private:
        float    radius;
    public:
        void Initialize(float input_radius)
        {    radius = input_radius;
        }
	float Circumference(void)
        {    return 2*3.1415926*radius;
        }
	float Area(void)
        {    return 3.1415926*radius*radius;
        }
};
Note that in this style, one does not need to prepend "Circle::" to the member function names.

Constructors and Destructors

The previous example included a member function called "Initialize" which initialized the value of the sole internal variable, radius, after the Circle variable pool was declared.

Since the process of initializing internal variables of classes is so common with classes containing member functions, this process can actually be performed when the class variables are declared. It is accomplished automatically via a member function called a constructor if such a constructor is included in the definition of the class.

A constructor is a member function of a class whose name is the name of the class. Its general purpose is to perform any sort of initialization work needed for the class and, in particular, to initialize any necessary variables. It can take arguments and can also include input or output functions, although such would be unusual, though not illegal.

Corresponding to a constructor is a destructor, a member function which performs whatever needs to be done right before the variable defined of a specific class ceases existence. To explain why constructors and destructors are useful or necessary, it may be helpful to explain the lifespan of variables in a language such as C++ in which certain variables are dynamically allocated while the program is running.

In C++, not all variables have memory locations allocated to them when the program is compiled. In particular, local variables in functions do not have any memory locations allocated to them until the function in which they are defined is invoked by the calling program or calling function. At that moment, space is allocated for variables and the variables are "incarnated" or "instantiated" until that function completes its lifespan and control is returned to the calling segment.

For example, there are no variables declared in the following main() segment, so on most systems, there would not be any memory locations allocated to any variables. If there were variables declared in void function func1(), any such variables would would have space allocated to them only after control is transferred from main() to func1(). Then, when control is returned to main() at the conclusion of func1(), such space would be relinquished for future possible reuse.

int main()
{
	cout << "-----before the first invocation of func1" << endl;
	cout << endl;
	func1(2);
	cout << endl;
	cout << "-----before the second invocation of func1" << endl;
	cout << endl;
	func1(4);
	cout << endl;
	cout << "that's all folks!" << endl;
	return 0;
}
Because this method of allocating and deallocating space is intrinsic to C++, one can take advantage of the fact that variables will come into existence when a function is called and will end their lifespan when a function is completed. We can perform any needed initialization and "clean-up" via constructors and destructors in class variables.

For the sake of example, we define the following class and give it the name DATALINE. It includes a constructor (by necessity given the same name as the name of the class, DATALINE), and a destructor (with the name ~DATALINE, i.e., the name of the class prepended by a tilde). (Note that in general it is not necessary for both to be included -- one can include one without the other.)

As a reminder, recall the following:

Note that in the following class example, the constructor and destructor untypically contain output statements to show that they actually perform their functions as explained.

class DATALINE {
   private:
	int	num;
   public:
	float 	balance;
//
// the following is the CONSTRUCTOR -- note that it has the same
// identifier as that for the entire class -- it is used primarily
// to initialize values of variables in the private section of the class.
//
	DATALINE (int i)
	   {cout << "==>beginning of constructor, i=" << i << endl;
	   num = i;
	   balance = i/3.0;
	   cout << "num=" << num << " bal=" << balance << endl;
	   }
//
// the following is the DESTRUCTOR -- it is the last thing done before
// a particular incarnation of this class ceases existence.
// 
	~DATALINE (void)
	   {cout << "-->end of this incarnation of a DATALINE var, bal=";
	    cout << balance << endl;}
};
This class, named DATALINE, contains four component parts: one public variable (balance), one private variable (num), and two public member functions (the constructor and destructor).

Suppose we complete the code by including a void function func1() with one integer input parameter.

void func1(int n)
// the following line declares var1 to be of type DATALINE and initializes
// the parameter i of the constructor to a value of 2.  The constructor
// in turn initializes the private variable num to the same value as i.
{DATALINE var1(2);
	cout << "inside func1, n = " << n << endl;
	var1.balance = n*2.5;
}
Note in the code that, since the constructor of the class DATALINE included one parameter, when we declare the local variable var1, we include a value in parentheses as if it were a function. This value is the value used as the input to the constructor of the class.

When we put all this code together (it is found at this link), and run the code, the logic of the code should be as follows:

The output is as follows:
-----before the first invocation of func1

==>beginning of constructor, i=2
num=2 bal=0.666667
inside func1, n = 2
-->end of this incarnation of a DATALINE var, bal=5

-----before the second invocation of func1

==>beginning of constructor, i=2
num=2 bal=0.666667
inside func1, n = 4
-->end of this incarnation of a DATALINE var, bal=10

that's all folks!
Constructors and destructors can be very useful when creating certain classes. On the other hand, in many (perhaps most) situations, there is no need for either or for both. Programmers should remember that C++ has the capacity to initialize component variables of classes and to perform necessary "clean-up" by means of constructors and destructors. But programmers need not force the "blueprint" of every class to include needlessly such member functions.


This page is maintained by Dennis C. Smolarski, S.J. dsmolarski@math.scu.edu
© Copyright 1998 Dennis C. Smolarski, SJ, All rights reserved.
Last changed: 5 December 1998.