CIS-255 Home: http://www.c-jump.com/bcc/c255c/c255syllabus.htm
Controlling object creation using constructors
Default constructors
Other types of constructors
Digression: default function arguments
Rules for implicit default constructors
Digression: dynamic memory allocation
Destructors
Digression: references
Controlling object copying
Objects are more useful if program can specify the details of their instantiation
This is done by declaring objects and controlling the semantics of object creation
The goal is to create objects which are always in a good state...
or at least identifiably in a bad state.
#include "point.h" int main() { Point pt( 5, 10 ); // construct object with initial values //... }
Every declaration of an object results in the calling of a constructor
If you don't provide a constructor, C++ does that for you, but this is not necessarily what you want.
You don't call constructors; a constructor is invoked at the time of object creation.
All constructors chain to sub-object constructors.
Sub-object constructors are invoked first:
|
|
How are constructors defined ?
// point.h class Point { public: Point(); private: int m_x; int m_y; }; // point.cpp // Default constructor is the one with no arguments: Point::Point() { m_x = 0; m_y = 0; }
// point.h class Point { public: Point(); // default constructor Point( int x, int y ); private: int m_x; int m_y; }; // point.cpp Point::Point( int x, int y ) { m_x = x; m_y = y; }
You lose the implicit default constructor, if:
you add a constructor taking no arguments:
class Point { public: Point(); };
you add a constructor with all default arguments:
class Point { public: Point( int x = 0, int y = 0 ); };
you add a constructor taking one or more arguments:
class Point { public: Point( Point const* other ); }; int main() { Point pt; // ERROR: no default constructor! return 0; };
You must have a default constructor to declare an array of objects:
// 10 calls to Point::Point() Point point_array[ 10 ];
C++ functions can have default parameters:
void print( int value, int base = 10 );
void f()
{
print( 31 ); // uses default
print( 31, 8 ); // overrides default
print( 31, 10 ); // same as first call
}
Same as
void print( int value, int base ); void print( int value ) { print( value, 10 ); }
But only trailing arguments can be defaulted:
void func_one( int x, int y = 0 ); // Ok
void func_two( int x = 0, int y ); // Error!
Early C++ allowed default args in function definitions:
// declaration
void print( int value, int base = 10 );
// definition
void print( int value, int base = 10 )
{
//...
}
Today's ISO C++ no longer permits it, so use:
// declaration void print( int value, int base = 10 ); // definition void print( int value, int base /* = 10 */ ) { //... }
|
|
A destructor is called for an object
when the object goes out of scope;
when the object containing it is destroyed.
Destructors chain to sub-objects in the exact reversed order of the constructors chain:
int main() { Moped honda; return 0; }// ~Moped() destructor is invoked: honda goes out of scope
|
|
|
|
The destructor will be called for any object at the end of its lifecycle
This provides the perfect opportunity to clean up
If your object does any dynamic resource allocation during its life, it should deallocate those resources in its destructor
Examples of resources deallocation could be:
freeing memory allocated with new
closing open files or devices
A C++ reference is like a pointer, except:
Usage syntax is like an object
References always refer to something
References can't be reassigned to something else
This is enforced by:
Requiring initialization in case of variables
The implicit reference initialization in case of function parameters
No syntax exists to changing the referent (the object that a reference is pointing to)
Once instantiated, the reference becomes eternal synonym for an object or a variable
void swap( int& left, int& right ) { int temp = left; left = right; right = temp; } void f() { int x = 8; int i = 4; int& ri = i; ++ri; // now i == 5 ri = x; // now i == 8 int& ref2 = 2; // error, RHS must be an lvalue }
Given:
int i = 4; int x = 8; int& r = i;
Silly attempts to get r to refer to x instead of i:
r = x; // NO: now i == 8 int& r = x; // NO: variable redefinitions are illegal r = &x; // NO: type mismatch, r not a pointer-type r& = x; // NO: plain syntax error &r = &x; // NO: left operand must be l-value
Conclusion: r refers to i for r's entire life.
|
|
Consider:
#include "point.h" void draw_segment( Point from, Point to ) { /* Connect two points */ } int main() { Point pt1( 5, 15 ); Point pt2( 10, 20 ); draw_segment( pt1, pt2 ); return 0; }
What should happen when objects are passed as parameters?
Again, we want to be able to control the semantics
|
|
How are copy constructors defined ?
// point.h class Point { public: // copy constructor Point( Point& other ); private: int m_x; int m_y; }; // point.cpp Point::Point( Point& other ) { m_x = other.m_x; m_y = other.m_y; }
Called when an object is passed to a function...
...but not if passed by reference!
Called when an object is returned from a function
Called when one object is used to initialize another:
Point pt1( 5, 10 ); Point pt2 = pt1; // Initializer syntax is preferred! Point pt3( pt1 ); // Constructor syntax -- avoid