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.
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.
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:
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:
-----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.