Aggregation (Composition) of Classes
Static Data
Members
Operator Overloading
Reading: Dietel 7.3, 7.7
Making new classes from previous ones
- We've seen examples of aggregation:
class Student
{
private:
int credits;
int ID;
string name;
public:
// member function prototypes
}- Only need the string interface (prototypes and descriptions) in order to use string members
- Can make use of previously written (and optimized) classes
- Aggregated classes can become members of other classes, etc.
Implicit functionality of aggregated classes
Constructors
- When an aggregated class constructor is called, parameterless constructors are called for its class-type members
- These are called before the constructor for the aggregated class
- Ensures correct initialization of class-type members
- Can override this behavior using initialization lists
class MyType
{
private: string name; Array a;
public:
MyType ( string s, int size ); // constructor
}MyType :: MyType ( string s, int size ) : name ( size ), a ( size ) { } // No other initialization necesary
Invokes non-default constructors for string and Array member
Without initialization list: call default constructors, then need to reassign
Destructors
- When an aggregated class destructor is called, destructors are called for its class-type members
- These are called after the destructor for the aggregated class
- Deallocate memory in reverse order of allocation
- Ensures correct clean-up and return of dynamically-allocated memory
Copy Constructors
- Default copy constructor calls copy constructors for all class-type members
- Programmer-defined copy constructors by default call parameterless constructors (not copy constructors!) for class-type members
- Can call copy constructors in initialization list instead if desired
Assignment Operators
- Default assignment operator uses assignment operator of each class-type member to assign
- Programmer-defined assignment operators do not do any implicit assignment; this must be done by the operator implementation
Example: Movie class
Movie.h
class Movie
{
public:
Movie ( ); // Constructors
Movie ( string title_parameter );
Movie ( string title_parameter, int n_stars );
Movie ( const Movie& rhs ); // Copy constructor
~Movie ( ); // Destructor
// Binary operators: implicit first parameter of type Movie
Movie& operator = ( const Movie& rhs ); // Overload =
bool operator == ( const Movie& rhs ); // Overload equality
bool operator != ( const Movie& rhs ); // Overload inequality -- doesn't come with ==
string
GetTitle ( ) const;
string GetStar ( int index = 0 ) const;
int GetNumStars ( ) const;
int GetID ( ) const;
void
SetTitle ( string title_parameter );
void SetStar ( string name, int index );
// No SetID ( ); we want to enforce unique IDs
private:
string title;
string* stars;
int num_stars;
int ID;
static int nextID; // Next available ID; enforce uniqueness
// Only one copy for the whole class, not each instantiation
}
// Also keep this prototype in Movie.h ; see below
ostream& operator << ( ostream& o, const Movie&
rhs )
Movie.cpp
int Movie :: nextID = 1; // No assignments possible within class definition
// Must assign a value exactly once
// Two possible parameterless constructors:
Movie :: Movie ( )
{
ID = nextID ++;
stars = 0; // represent a null pointer
num_stars = 0;
// Default string constructor called: title is
empty string
}
OR
Movie :: Movie ( ) : title ( "No title" )
{
ID = nextID ++;
stars = 0;
num_stars = 0;
// Could also have said: title = string("No
title"); in constructor body
}
Movie :: Movie ( string title_parameter ) : title ( title_parameter )
{
ID = nextID ++;
stars = 0;
num_stars = 0;
}
Movie :: Movie ( string title_parameter, int n_stars ) : title (
title_parameter )
{
ID = nextID ++;
num_stars = n_stars;
stars = new string [ n_stars ]; // Each string default
initialized by new
}
Movie :: Movie ( const Movie& rhs ) : title ( rhs.title )
{
ID = rhs.ID;
num_stars = rhs.num_stars;
stars = new string [ num_stars ]; // Default constructor used
here for each string
for ( int i=0; i < num_stars; i++)
stars [ i ] = rhs.stars [ i ]; //
Overloaded = used here for each string
}
Movie :: ~Movie ( )
{
delete [ ] stars; // Has no effect on null pointers
// string destructor will be called on title
and each element of stars array
}
string Movie :: GetTitle ( ) const
{
return title;
}
int Movie :: GetNumStars ( ) const
{
return num_stars;
}
string GetStar ( int index ) const
{
if ( 0 <= index && index < num_stars )
return stars [ index ];
return string("Error: index out of range."); //
Explicit constructor call
// Could also throw an exception
}
int Movie :: GetID ( ) const
{
return ID;
}
void Movie :: SetTitle ( string title_parameter )
{
title = title_parameter; // overloaded string
assignment used
}
void Movie :: SetStar ( string title_parameter, int index )
{
if ( 0 <= index && index < num_stars )
stars [ index ] = title_parameter;
// Could throw an exception if index out of range
}
Movie& Movie :: operator = ( const Movie& rhs )
{
if ( this != &rhs ) // Don't clobber on
self-assignment
{
ID = rhs.ID;
num_stars = rhs.num_stars;
title = rhs.title;
// Assignment operator for string used here
delete [ ] stars;
stars = new string [ num_stars ] ; //
Default constructor for each string used here
if ( stars == 0 )
num_stars =
0; // Or could throw an exception
}
return *this;
}
bool Movie :: operator == ( const Movie& rhs )
{
return ( ID == rhs.ID );
}
// Could also use: if ( this.ID == rhs.ID ) return
true; else return false;
bool Movie :: operator != ( const Movie& rhs )
{
return ! ( *this == rhs );
}
// Could also use: if ( this.ID != rhs.ID ) return
true; else return false;
// Overload << for Movie objects : cout << M;
// Cannot be a member function / operator of Movie, since lhs is of type ostream,
not Movie
// Can keep in Movie.cpp ; part of interface to Movie class
ostream& operator << ( ostream& o, const Movie&
rhs )
{
o << "Title: " << rhs.GetTitle( ) <<
endl; // String class has also overloaded <<
o << "Stars: " << endl;
for ( int i=0; i < rhs.GetNumStars ( ); i++ )
o << rhs.GetStar ( i ) <<
" ";
return o;
}