|
|
Summary
• Objects as Class Members
• Example 1
• Example 2
• Advantages of Objects as Class Members
• Structures as Class Members
• Classes inside Classes
• Tips
Objects as Class Members
A class is a user defined data type and it can be used inside other classes in the
same way
as native data types are used. Thus we can create classes that contain objects of
other
classes as data members.
When one class contains objects of other classes, it becomes mandatory to understand
how and in what sequence the contained and containing objects are constructed. An
important point in construction of an object is that the contained data members
of the
object (regardless whether they are native or user defined data types) are constructed
before the object itself. The order of destruction of an object is reverse to this
construction order, where the containing object is destroyed first before the contained
objects.
To elaborate the construction and destruction orders of objects, we take a class
A and
contain its instance (object) in another class
B.
Page 512
/* This program illustrates the construction and destruction orders of objects.
*/
#include <iostream.h>
class A
{
public:
A()
{
cout << "\n A Constructor ...";
}
~A()
{
cout << "\n A Destructor ...";
}
};
class B
{
public:
B()
{
cout << "\n B Constructor ...";
}
~B()
{
cout << "\n B Destructor ...";
}
private:
A a;
};
void main(void)
{
B b;
}
The output of this code is as follows:
A Constructor ...
B Constructor ...
B Destructor ...
A Destructor ...
Page 513
In the code above, we have contained an instance of the class
A inside class
B. In the
main function, we have only created an object of the class
B.
From the output, we can see the first line that the contained object
a’s default constructor
is called before the default constructor of the class
B. At destruction time,
the destructor
of the class B is
called first before A’s.
Note that the contained object ‘a’
of class A
is
constructed by calling the default constructor. Hence, we have found one way of
constructing contained objects by means of default constructors and then setting
the
values of data members by calling setter methods of the object. But this is cumbersome
and wasteful, we have a better way provided by the language to initialize contained
objects at construction time using the
initializer list. Initializer
list is used to initialize the
contained objects at the construction time.
Example 1
Let’s take a class of PersonInfo
that stores name,
address and
birthday of a person.
This
PersonInfo class contains an instance of our veteran
Date class to store
birthday of a
person.
class PersonInfo
{
public:
// public member functions...
private:
char name[30];
char address[60];
Date birthday; // member object
};
This declaration specifies a
Date object birthday
as a private
data member. Note that no
arguments are specified in the declaration of
birthday. However, this
does not mean that
the default constructor is called when the
PersonInfo object is constructed
but we can
always specify a member initializer to call a parameterized constructor.
A colon is placed after the parameter list of the containing class's constructor,
followed
by the name of the member and a list of arguments as shown below:
class PersonInfo
{
public:
PersonInfo( char * nm, char * addr, int month, int day, int year );
// ...
private:
// ...
};
Page 514
PersonInfo::PersonInfo( char * nm, char * addr, int month, int day, int year )
: birthday( month, day, year ) // Member initializer
{
strncpy( name, nm, 30 );
strncpy( address, addr, 60 );
}
Note that there are five parameters inside
PersonInfo constructor
including the three
parameters month,
day and
year parameters to be passed
to Date class’s
parameterized
constructor as birthday( month,
day, year ). We are using the initializer list, therefore,
there is no need to call setter methods of the
Date class to initialize
the birthday object.
Similarly, multiple contained objects can be initialized by using comma separated
initializers. The order of the execution of initializers is the same as the order
of
declarations of objects inside the outer class. To confirm about the order of execution,
let
us have another Date
object drvLicenseDate
declared after birthday
object in the
PersonInfo class:
/* This program illustrates the initializer list, order of execution of constructor’s
inside
the list. */
#include <iostream.h>
#include <string.h>
class Date
{
public:
Date( );
Date(int month, int day, int year);
~Date ( );
private:
int month, day, year;
};
Date::Date( )
{
cout << "\n Date -- Default constructor called ...";
month = day = year = 0;
}
Date::Date(int month, int day, int year)
{
cout << "\n Date -- Constructor with month=" << month
<< ", day= " << day << ", year= " << year << " called ...";
this->month = month;
Page 515
this->day = day;
this->year = year;
}
Date::~Date ( )
{
cout << "\n Date -- Destructor called ...";
}
class PersonInfo
{
public:
// public member functions...
PersonInfo( char * nm, char * addr, int month, int day, int year,
int licMonth, int licDay, int licYear );
PersonInfo::~PersonInfo();
private:
char name[30];
char address[60];
// member objects
Date birthday;
Date drvLicenseDate;
};
PersonInfo::PersonInfo( char * nm, char * addr, int month, int day, int year,
int licMonth, int licDay, int licYear )
: drvLicenseDate( licMonth, licDay, licYear), birthday( month, day, year )
// Above line is initializer list
{
cout << "\n PersonInfo -- Constructor called ...";
strncpy( name, nm, 30 );
strncpy( address, addr, 60 );
}
PersonInfo::~PersonInfo()
{
cout << "\n PersonInfo -- Destructor called ...";
}
main(void)
{
PersonInfo pi("Abbas", "12-Y, DHS, Lahore, Pakistan", 12, 12, 1972, 12, 10, 1992);
}
Page 516
The output of this program is:
Date -- Constructor with month=12, day= 12, year= 1972 called ...
Date -- Constructor with month=12, day= 10, year= 1992 called ...
PersonInfo -- Constructor called ...
PersonInfo -- Destructor called ...
Date -- Destructor called ...
Date -- Destructor called ...
Because birthday
is declared before drvLicenseDate,
it is clear from the output that the
constructor for birthday
is called first and then for the
drvLicenseDate object,
although
drvLicenseDate is present before
birthday in the initializer
list.
Example 2
Let’s take another example to work with the size of a matrix. We declare a
Column class
first then a Row
class. Row
class contains an instance of
Column class to store the
number
of columns (number of elements) inside one
Row instance. Further,
the Matrix class
contains an instance of Row
class. See the code below.
/* Program to illustrate the initialization lists, construction and destruction
sequences of
contained and containing objects. */
#include <iostream.h>
#include <stdlib.h>
class Column
{
private :
int size ;
public :
Column ( int size )
{
cout << "Column created" << endl << endl ;
this->size = size ;
}
~Column ( )
{
cout << "Column destroyed " << endl << endl ;
}
void showSize ( ) ;
void setSize ( int ) ;
};
void Column :: showSize ( )
Page 517
{
cout << "Column size is : " << size << endl << endl ;
}
void Column :: setSize ( int sz )
{
size = sz ;
}
class Row
{
private :
int size ;
Column col ;
public :
Row ( int rowSize, int colSize ) : col( colSize )
{
cout << "Row created" << endl << endl ;
this->size = rowSize ;
}
~Row ( )
{
cout << "Row destroyed " << endl << endl ;
}
void showSize ( ) ;
void setSize ( int ) ;
};
void Row :: showSize ( )
{
col.showSize ( ) ;
cout << "Row size is : " << size << endl << endl ;
}
void Row :: setSize ( int sz )
{
size = sz ;
}
class Matrix
{
private :
Row row ;
public :
Matrix ( int rowSize, int colSize ) : row( rowSize, colSize )
{
cout << "Matrix created" << endl << endl ;
Page 518
}
~Matrix ( )
{
cout << "Matrix destroyed" << endl << endl ;
}
void displayMatrixSize ( ) ;
} ;
void Matrix :: displayMatrixSize ( )
{
row.showSize ( ) ;
}
void f( )
{
Matrix matrix(3, 4) ;
matrix.displayMatrixSize ( ) ;
}
int main()
{
f( );
system("PAUSE");
return 0;
}
The output of the program is as follows:
Column created
Row created
Matrix created
Column size is : 4
Row size is : 3
Matrix destroyed
Row destroyed
Column destroyed
Press any key to continue . . .
Page 519
Notice the construction sequence of objects. In order to create a
Matrix object, a
Row
object is created first and to create a
Row object, a
Column object is created.
So the
contained object Column
is constructed first of all, then comes the
Row object and finally
the Matrix object.
At destruction time, the very first object to destroy is the last object
constructed, which is the Matrix
object. The second object destroyed is
Row object and
then the Column
object at the end. See also the use of initializer list in the code, how the
colSize and rowSize
arguments are passed to the constructors.
The public data
members of a contained object can also be accessed from outside of the
containing class. For example, if
row object inside
Matrix class is
declared as public
and
has a public variable
named size then
it can be accessed using the dot operator (“.”) as:
int main ( void )
{
Matrix matrix ( 4, 5 ) ;
Matrix.row.size = 8 ;
}
Advantages of Objects as Class Members
It is a way of reusing the code when we contain objects of our already written classes
into
a new class. For example, Date class can be used as data member of Student, Employee
or PersonInfo class. In this approach, we don’t have to test our previously written
classes
again and again. We write a class, test it once and add it into our components
to
use it later.
It gives clarity and better management to the source code of our programs when we
break
up problems into smaller components. The smaller components can be managed
independently from their contained objects forming their own classes. For example,
in the
previous example program, Matrix
was subdivided into
Row and
Column classes.
When we declare an object as a constant data member inside a class then that constant
object is initialized using the initializer list. Therefore, a class, whose object
is contained
as const object,
must have a parameterized constructor.
Structures as Class Members
We have already studied that structures and classes are very similar in C++ except
the
default scope of members. The default scope for members of structures is
public whereas
the default visibility for class members is
private.
Page 520
Likewise, objects of different classes can act as data members, structures and unions
can
also act as data members of a class. In fact, all the discussion above for
Class Objects as
Class Members applies to this topic of
Structure Objects as Class Members.
#include <iostream.h>
#include <stdlib.h>
struct VehicleParts
{
int wheels;
int seats;
VehicleParts()
{
cout << "\n VehicleParts - default constructor";
}
VehicleParts(int wheels, int seats)
{
this->wheels = wheels;
this->seats = seats;
cout << "\n VehicleParts - parameterized constructor";
}
~VehicleParts()
{
cout << "\n VehicleParts - destructor" << endl;
}
} ;
class Vehicle
{
private :
VehicleParts vehicleParts ;
public :
Vehicle( )
{
cout << "\n Vehicle - default constructor" << endl;
}
Vehicle( int a, int b ) : vehicleParts( a, b )
{
cout << "\n Vehicle - parameterized constructor";
Page 521
}
~Vehicle( )
{
cout << "\n Vehicle - destructor";
}
void setPartsNum ( int a, int b )
{
vehicleParts.wheels = a ;
vehicleParts.seats = b ;
}
void displayNumVehicleParts ( )
{ /* The data members of the structure are public,
therefore, directly accessible from outside. */
cout << "\n Number of wheels for this vehicle are "
<< vehicleParts.wheels;
cout << "\n Number of seats for this vehicle are "
<< vehicleParts.seats << endl;
}
} ;
void f()
{
Vehicle car( 4, 2 ) ;
car.displayNumVehicleParts( ) ;
}
void main ( )
{
f();
system ( "PAUSE" ) ;
}
The output of the program is:
VehicleParts - parameterized constructor
Vehicle - parameterized constructor
Number of wheels for this vehicle are 4
Number of seats for this vehicle are 2
Vehicle - destructor
VehicleParts - destructor
Press any key to continue . . .
Page 522
Classes inside Classes
In C language, structures can be defined inside structures, Similarly in C++, we
can have
structures or classes defined inside classes. Classes defined within other classes
are called
nested classes.
A nested class is written exactly in the same way as a normal class. We write its
data
members, member functions, constructors and destructors but no memory is allocated
for
a nested class unless an instance of it is created. C++ allows multiple levels of
nesting.
Importantly, we should be clear about the visibility of the nested class. If a class
is nested
inside the public
section of a class, it is visible outside the outer (enclosed) class. If
it is
nested in the private
section, it is only visible to the members of the outer class. The outer
class has no special privileges with respect to the inner class. So, the inner class
still has
full control over the accessibility of its members by the outer class. Interestingly,
the
friend operator can be used to declare enclosed class as a friend of
inner class to provide
access to inner class’s private
members. This operator is used in the same way as we use
it for other classes that are not nested. We can also make the inner class to access
the
private members of enclosed class by declaring the inner class as a
friend of outer
class.
The reason of nesting classes within other classes is simply to keep associated
classes
together for easier manipulation of the objects.
/* This program illustrates the nested classes */
#include <iostream.h>
#include <stdlib.h>
class Surround
{
public :
class FirstWithin
{
public:
FirstWithin ()
{
cout << "\n FirstWithin - default constructor";
}
~FirstWithin()
{
cout << "\n FirstWithin - destructor";
}
int getVar() const
{
return (variable);
}
private:
int variable;
Page 523
};
FirstWithin myFirstWithin;
private:
class SecondWithin
{
public:
SecondWithin()
{
cout << "\n SecondWithin - default constructor";
}
~SecondWithin()
{
cout << "\n SecondWithin - destructor ";
}
int getVar() const
{
return (variable);
}
private:
int variable;
};
// other private members of Surround
};
void f(void)
{
Surround::SecondWithin a;
Surround::FirstWithin b;
Surround c;
c.myFirstWithin.getVar();
}
int main()
{
f();
cout << endl << " ";
system("PAUSE");
return 0;
}
The output of the program is as follows:
Page 524
SecondWithin - default constructor
FirstWithin - default constructor
FirstWithin - default constructor
FirstWithin - destructor
FirstWithin - destructor
SecondWithin - destructor
Press any key to continue . . .
Notice the access specifier ( :: ) usage in function
f() to access the members
of inner class.
The class FirstWithin
is visible both outside and inside
Surround. The class
FirstWithin
has therefore global scope. The constructor
FirstWithin() and the member
function
getVar() of the class
FirstWithin are also globally
visible. The int variable
data member
is only visible for the members of the class
FirstWithin as it is declared
private. Neither
the members of Surround
nor the members of SecondWithin
can access the variable of
the class FirstWithin
directly. The class
SecondWithin is visible only inside
Surround.
The public members
of the class SecondWithin
canalso be used by the members of the
class FirstWithin,
as nested classes can be considered members of their surrounding class.
The constructor SecondWithin()
and the member function
getVar() of the class
SecondWithin can also only be reached by the members of
Surround (and by the
members of its nested classes). The
int variable data member
of the class SecondWithin
is only visible to the members of the class
SecondWithin. Neither the
members of
Surround nor the members of
FirstWithin can access
the variable of the class
SecondWithin directly.
The nested classes can be considered members of the surrounding class, but the members
of nested classes are not members of the surrounding class. So, a member of the
class
Surround may not access
FirstWithin::getVar() directly.
The nested classes are only
available as type names. They do not imply as objects containment by the surrounding
class. If a member of the surrounding class uses a (non-static) member of a nested
class
then a pointer to a nested class object or a nested class data member is defined
in the
surrounding class. The pointer is further used by the members of the surrounding
class to
access members of the nested class.
It is important to know how do we define Member functions of nested classes. They
may
be defined as inline functions or they can also be defined outside of their surrounding
class. Consider the constructor of the class
FirstWithin in the previous
example.
The constructor FirstWithin()
is defined in the class
FirstWithin, which is,
in turn,
defined within the class Surround.
Consequently, the class scopes of the two classes must be used to define a
constructor as
the following:
Surround :: FirstWithin :: FirstWithin ( )
{
variable = 0 ;
}
The classes FirstWithin
and SecondWithin
are both nested within
Surround, and can be
considered members of the surrounding class. Since members of a class may directly
Page 525
refer to each other, members of the class
SecondWithin can refer
to public members
of
the class FirstWithin
but they cannot access
private members of the
FirstWithin unless
SecondWithin is declared as a
friend of
FirstWithin.
See the code snippet below, we have used
friend operator here extensively
so that all the
three classes Surround,
FirstWithin and
SecondWithin can
access private members
of
each other.
class Surround
{
class SecondWithin ;
public :
class FirstWithin
{
friend class Surround ;
friend class SecondWithin ;
public :
int getValue()
{
Surround :: variable = SecondWithin :: variable ;
return (variable);
}
private :
static int variable ;
} ;
friend class FirstWithin ;
int getValue ( )
{
FirstWithin :: variable = SecondWithin :: variable ;
return (variable) ;
}
private :
class SecondWithin
{
friend class Surround ;
friend class FirstWithin ;
public :
int getValue ( )
{
Surround::variable = FirstWithin::variable;
return (variable) ;
}
private:
static int variable;
};
friend class SecondWithin ;
static int variable;
Page 526
};
We can also define structures inside classes in the same manner as we defined classes
within classes. Again, all the above discussion is valid for structures inside classes
except
the default scope of members in structures is
public unless explicitly
declared otherwise.
Tips
• A class can contain instances of other classes as its data members.
• It is a way of reusing the code when we contain objects of our already
written
classes into a new class.
• The inner data members of the object are constructed and then the object
itself.
The order of destruction of an object is reverse to this construction order, where
the outer object is destroyed first before the inner data members.
• Initializer list is used to initialize the inner objects at the construction
time.
• In C++, we can have structures or classes defined inside classes. Classes
defined
within other classes are called nested classes.
|
|
|
|