Thursday, July 24, 2008

The Class: Data Encapsulation, Data Hiding, and Objects

Like a C structure, a C++ class is a data type. An object is simply an instantiation of a class. C++ classes have additional capabilities as the following example should show:

Vector v1(1,2),
Vector v2(2,3),
Vector vr;
vr = v1 + v2;

Vector is a class. v1, v2, and vr are objects of class Vector. v1 and v2 are given initial values through their constructor. vr is also initialized through its constructor to certain default values. The example illustrates a major power of C++. Namely, we can define functions on a class as well as data members. Here, we have an overloaded addition operator which makes our expression involving Vectors seem much more natural than the equivalent C code:

Vector v1, v2, vr;
add_vector( &vr , &v1, &v2 );

The ability to define these member functions allows us to have a constructor for Vector, code that creates an object of class Vector. The constructor ensures proper initialization of our Vectors.

Though not illustrated in the above example, a class can limit the use of its data members and member functions by non-member code. This is encapsulation. If class K defines member M as private, then only members of class K can use M. Defining M as public means any other class or function can use M.

Let's take a look at a trivial implementation of Vector that will show is a little about constructors, operators, and references.

#include 
class Vector
{
public:
Vector(double new_x=0.0,double new_y=0.0) {
if ((new_x<100.0) && (new_y<100.0))
{
x=new_x;
y=new_y;
}
else
{
x=0;
y=0;
}
}
Vector operator +
( const Vector & v)
{
return
(Vector (x + v.x, y + v.y));
}
void PrintOn (ostream& os)
{
os << "["
<< x
<< ", "
<< y
<< "]";
}

private:
double x, y;
};

int main()
{
Vector v1, v2, v3(0.0,0.0);
v1=Vector(1.1,2.2);
v2=Vector(1.1,2.2);
v3=v1+v2;
cout << "v1 is ";
v1.PrintOn (cout);
cout << endl;
cout << "v2 is ";
v2.PrintOn (cout);
cout << endl;
cout << "v3 is ";
v3.PrintOn (cout);
cout << endl;
}

Encapsulation of x and y means that they cannot be altered without the help of specific member functions. Any member function or data member of Vector can use x and y freely. For everyone else, the member functions provide a strict interface. They ensure a particular behavior in our objects. In the example above, no Vector can be created that has an x or y component that exceeds 100. If at some point code tries to do this, then the constructor performs bounds-checking and sets x and y both to zero. In a normal C structure we can simply do the following:

Vector v1;
InitVector( & v1, 99 , 99 );
v1.x = 1000;

InitVector() closely approximates a C++ constructor. Assume it tries to behave like the constructor Vector() in example three. This C code demonstrates how without encapsulation we can easily violate the rules set up in our pseudo-constructor. With class Vector, both x and y are private. As a result, they can only be accessed by member functions. If our goal is to prevent x and y from exceeding 100, we simply have all accessor functions perform bounds-checking. In fact, once created and outside of the addition operation there's no way to modify x or y. They are private and no member function, outside of the constructor, sets their values. Notice how the constructor Vector() limits our Vector component values. By returning a new object, the addition operator uses the constructor to check for overflow.

We could have made `+' do multiplication instead. Though such manipulation is atypical, it can be quite useful. For example, C++ comes standard with a streams library which uses the << operator to provide output.

There is one useful thing about the addition operator: we don't have to pass the addresses of arguments. The arguments for the addition operator specify that the parameters are references (using the reference operator &-not the same as the address-of operator &). Recall that the reference operator allows us to use the same calling syntax as call-by-value and yet modify the value of an argument. The reference operator avoids the overhead of actually creating a new object. Thus, we can avoid a lot of indirection.

However, the most powerful OOP extension C++ provides is probably inheritance. Classes can inherit data and functions from other classes. For this purpose we can declare members in the base class as protected: not usable publicly but usable by derived classes.

In conclusion, we looked at some of the features that make C++ a better C. C++ provides stronger type checking by checking arguments to functions and reduces syntactic errors through the use of reference types. Programmers can also add new types to the language by defining classes. Although we have only taken a brief look at classes, we will see more abstract discussion of C++ object-orientation as well as general OOP concepts in upcoming columns.

No comments: