Topic 7: C++ Classes
Synopsis
Classes¤
- 
The fundamental unit of data type abstraction¤
and encapsulation¤
  
- 
A class¤
specifies how the memory used by objects is laid-out (just like a C struct
does)
  
- 
Also specifies which functions are allowed to access parts of that memory
(unlike a C struct)
  
Declaring classes¤
- 
Consider a C struct and associated functions:
 
/* C VERSION OF Vehicle ADT */
struct VehicleRec
{
        int   myYear;
        char* myOwner;
        char* myRegNum;
};
typedef struct VehicleRec Vehicle;
void v_Init_void(Vehicle* v);
void v_Init_data(Vehicle* v, char* regnum, int year);
void v_Cleanup(Vehicle* v);
void v_RegisterTo(Vehicle* v, char* name);
void v_CheckRecord(Vehicle* v, char* name);
In C++, we could create an equivalent (but more powerful) ADT by declaring
a class¤:
                class Vehicle
{
public:
        Vehicle(void);
        Vehicle( char* regnum, int year);
        ~Vehicle(void);
        void RegisterTo(char* name);
private:
        int   myYear;
        char* myOwner;
        char* myRegNum;
        void CheckRecord(char* name);
};
Note that the data fields (members¤)
and the associated functions are declared together, all in the class¤.
 
Class¤
data members¤¤
- 
Can be of any inbuilt or user-defined type
  
- 
Declared and used just like the fields in a struct
  
- 
Except that they are usually not accessible to any function which
has access to a Vehicle object!
  
- 
Store the "state¤"
of an instance¤
of the class¤
  
 
Class¤
function members¤¤
- 
Are functions which can only be called for objects of a particular class¤
  
- 
Are usually accessible anywhere that their object is available
  
- 
Represent the "behaviour¤"
of an instance¤
of a class¤
  
- 
Because all functions are defined for the class¤
as a whole every instance¤
of that class¤
has the same behaviour¤
  
- 
May be only declared in the class¤
(as above), or may be fully defined in the class¤:
 
class Vehicle
{
public:
        Vehicle(void);
        Vehicle( char* regnum, int year);
        ~Vehicle(void);
        void RegisterTo(char* name)
        {
                CheckRecord(name);
                myOwner = name;
        }
private:
        int   myYear;
        char* myOwner;
        char* myRegNum;
        void CheckRecord(char* name);
};
If not fully defined in the class¤
declaration, must be defined somewhere else (usually in a .C code file):
void Vehicle::CheckRecord(char* name)
{
        if (wantedFelon(name))
        {
                cerr << "There are outstanding warrants on "
                     << name << endl
                     << "Do not allow this person to register "
                     << myRegNum << "!" << endl;
        }
}
Member¤
access control
- 
The public: and private: keywords specify
what kind of access is available to the various members¤
of the class¤
  
- 
Members¤
declared in the public: section can be accessed anywhere
(just like the members¤
of a C struct)
  
- 
But members¤
declared in the private: section can only be accessed by
other class¤
member¤
functions¤
  
- 
You can think of the access like this:
  
  
- 
Usually put all data members¤¤
in the private: section (encapsulation¤)
and most function members¤¤
in the public: section
  
- 
A class¤
can have multiple public: and private:
sections. The current accessibility¤
is determined by the immediately preceding keyword
  
- 
There's a third (intermediate) level of accessibility¤
(protected:), which we'll discuss when we look at C++ inheritance
  
Creating objects of
a given class¤
- 
Each class¤
declaration creates a new type (just like the C "struct-then-typedef"
approach)
  
- 
Can create instances¤
(objects) of that new type with the normal variable declaration or dynamic
allocation syntaxes:
 
#include "Vehicle.h"
Vehicle gv1;    // GLOBAL Vehicle OBJECT
int main(void)
{
        Vehicle lv1;    // LOCAL Vehicle OBJECT
        Vehicle lv2;    // LOCAL Vehicle OBJECT
        Vehicle* vptr = new Vehicle;    // DYNAMIC ALLOCATION
        // ETC...
}
Like ordinary C variables, non-dynamically-allocated objects cease to exist
at the end of the scope in which they are declared
 
Accessing class¤
members¤
- 
Access to all accessible class¤
members¤
(including member¤
functions¤!)
of an object is via the '.' operator
   
#include "Vehicle.h"
int main(void)
{
        Vehicle lotusEsprit;
        lotusEsprit.RegisterTo("Jon McCormack");   // DREAM ON!
}
  
However, if we have a pointer to an object, we can use the '->' notation:
  #include "Vehicle.h"
int main(void)
{
        Vehicle* car;
        car = new Vehicle;
        car->RegisterTo("Jon McCormack");
}
  
Constructors¤
- 
In C (and C++), inbuilt types and normal structs can be initialized:
 
/* NORMAL C INITIALIZATIONS */
int cost = 250000;
struct StudRec sr = { "Lee", 12345678, 99.9 };
In C++ we can associate a function with each class¤
which allows us to initialize objects of that class¤
when they are constructed (like we provided v_Init_void()
and v_Init_data() in the original C version)
 
Such functions are called constructors¤
 
A class¤'s
constructors¤
have the same name as the class¤,
and don't specify a return type (not even void!)
 
Like other member¤
functions¤.
they may be defined in the class¤
declaration, or later:
class Vehicle
{
public:
        Vehicle(void)           // CONSTRUCTOR DEFINITION
        {
                myYear   = -1;
                myOwner  = 0;
                myRegNum = 0;
        }
        Vehicle(char* regnum, int year);        // CONSTRUCTOR
// DECLARATION
        // ETC. AS BEFORE
private:
        int   myYear;
        char* myOwner;
        char* myRegNum;
};
Vehicle::Vehicle(char* regnum, int year)
{
        myYear   = year;
        myOwner  = 0;
        myRegNum = new char[strlen(regnum)+1];
        strcpy(myRegNum, regnum);
}
Note that the contructors, being members¤
of the class¤,
have access to the data members¤¤,
even though the data members¤¤
are private
 
When one or more constructors¤
are defined for a class¤,
one of them is called whenever an object of that class¤
is created
 
The constructor¤
with a void parameter list is called if the object is created without any
useful information being provided:
Vehicle v;                      // CALLS v.Vehicle()
Vehicle* vptr = new Vehicle;    // CALLS vptr->Vehicle()
However, if a set of arguments is given after the declaration (or the call
to new), then the constructor¤
with the corresponding parameter list is called:
// THESE CALL Vehicle(char*,int) CTOR
Vehicle v ("DMC-042", 1978);
        // CALLS v.Vehicle("DMC-042",1978)
Vehicle* vptr = new Vehicle ("AAA-111", 1992);
        // CALLS vptr->Vehicle("AAA-111",1992)
Note that the multiple constructors¤
are an example of function overloading (same name, different signatures¤)
 
Destructors¤
- 
Sometimes it's also useful to do something when an object ceases to exist
(i.e. when it goes out of scope, or is deallocated using delete).
Why?
  
- 
C++ provides a means of specifying a function which is called automatically
whenever a object is about to cease to exist
  
- 
Such a function is called a destructor¤,
and always takes the name of its associated class¤
preceded by a '~' For example:
 
class Vehicle
{
public:
        // CTORS WOULD BE HERE
        ~Vehicle(void)          // DESTRUCTOR
        {
                delete[] myOwner;
                delete[] myRegNum;
        }
        // ETC. AS BEFORE
private:
        int   myYear;
        char* myOwner;
        char* myRegNum;
};
Destructor¤
is called when a locally-declared object goes out of scope, or when a dynamically-allocated
object is deleted, or for a global object at the end of the program:
Vehicle gv1;
int main(void)
{
        Vehicle lv1;
        Vehicle lv2;
        Vehicle* vptr1 = new Vehicle;
        Vehicle* vptr2 = new Vehicle;
        delete vptr2;  // vptr2->~Vehicle() CALLED DURING delete
}  // lv2.~Vehicle() AND lv1.~Vehicle() CALLED AT THIS POINT
// [END OF FILE]
// gv1.~Vehicle() CALLED AT THIS POINT
Where is the destructor¤
for the object pointed to by vptr1 called?
 
There can be only one destructor¤
per class¤.
Why?
 
The this pointer
- 
Recall that each of the v_... functions in the original
C version took a Vehicle* as their first argument. Why?
  
- 
The corresponding C++ class¤
members¤
didn't need a pointer to the object on which they operated. Why?
  
- 
However, such a pointer is available to those member¤
functions¤,
should they require it.
  
- 
It's called: this
  
- 
Hence we could rewrite the various member¤
functions¤:
 
class Vehicle
{
public:
        Vehicle(void);
        Vehicle( char* regnum, int year)
        {
                this->myYear   = year;
                this->myOwner  = 0;
                this->myRegNum = regnum;
        }
        ~Vehicle(void);
        void RegisterTo(char* name)
        {
                this->CheckRecord(name);
                this->myOwner = name;
        }
private:
        int   myYear;
        char* myOwner;
        char* myRegNum;
        void CheckRecord(char* name);
};
void Vehicle::CheckRecord(char* name)
{
        if (wantedFelon(name))
        {
                cerr << "There are outstanding warrants on "
                     << name << endl
                     << "Do not allow this person to register "
                     << this->myRegNum << "!" << endl;
        }
}
For members¤
of the Vehicle class¤
it's a Vehicle * const  (not a const Vehicle*)
 
Reading
- Lippman & Lajoie
 
- New: Chapter 13.
 
 
- Stroustrup
 
    
      - New: Chapter 10.
 
      
       
       
    
  
  This material is part of the CSE2305 - Object-Oriented 
  Software Engineering course. 
  Copyright © Jon McCormack & Damian Conway, 1998–2005. All rights 
  reserved.