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 will be more useful if:
we can control the semantics of instantiation (read: declaring) them.
The goal is to provide the client with objects which are always in a good (or at least identifiably bad) state:
#include "point.h" void 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, one will be provided for you, free of charge...
...and you'll get what you pay for.
You already know how to call constructors (see first bullet).
All constructors "chain" to sub-object constructors first:
class Cylinder {}; class Transmission {}; class Wheel {}; class Engine { Cylinder m_cylinder[ 2 ]; }; class Moped { Engine m_engine; Transmission m_transmission; Wheel m_wheel[ 2 ]; }; void main() { Moped honda; } |
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:
|
class Point { public: Point(); }; |
|
class Point { public: Point( int x = 0, int y = 0 ); }; |
|
class Point { public: Point( Point const* other ); }; void main() { Point pt; // Error: no default constructor: } |
// 10 calls to Point::Point() Point point_array[ 10 ];
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
}
void print( int value, int base ); void print( int value ) { print( value, 10 ); }
void func_one( int x, int y = 0 ); // Ok
void func_two( int x = 0, int y ); // Error!
void print( int value, int base = 10 );
void print( int value, int base = 10 )
{
//...
}
void print( int value, int base = 10 ); void print( int value, int base /* = 10 */ ) { //... }
#include "point.h" void main() { int* ptr_2_int = new int; int* ptr_2_array = new int[ 100 ]; int size = 1000; Point* ptr_points = new Point[ size ]; //... delete ptr_2_int; delete[] ptr_2_array; delete[] ptr_points; }
delete ptr;
is harmless.
A destructor is called for an object,
when it goes out of scope;
when the object containing it is destroyed.
If you don't provide a destructor, one will be provided for you,
free of charge...
...you know the rest.
void main() { Moped honda; }// ~Moped() destructor is invoked: honda goes out of scope
Chain of constructors: |
Chain of destructors: |
|
// point.h class Point { public: ~Point(); // destructor private: int m_x; int m_y; }; |
// point.cpp #include <iostream> #include "point.h" Point::~Point() { std::cout << "Goodbye..."; } |
|
// main.cpp #include "point.h" void main() { Point pt; }// ~Point() destructor is invoked here |
The destructor will be called for any object at the end of its lifetime.
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:
A 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
(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.
References can be returned by functions.
Why? To avoid copying overhead.
Generally used to return one of function arguments.
DON'T return references to locals !
int& counter() { static int count = 0; ++count; return count; // OK, count is static } int& disaster() { int count = calculate(); return count; // Oh, no!.. }
|
// point.h class Point { public: int& refx(); private: int m_x; int m_y; }; |
// point.cpp #include "point.h" int& Point::refx() { return m_x; } |
|
// main.cpp #include "point.h" void main() { Point pt; pt.refx() = 14; } |
Consider:
#include "point.h" void draw_segment( Point from, Point to ) { /* Connect two points */ } void main() { Point pt1( 5, 15 ); Point pt2( 10, 20 ); draw_segment( pt1, pt2 ); }
What should happen when objects are passed as parameters?
Again, we want to be able to control semantics.
If you don't provide a copy constructor, one will be provided for you,
free of charge...
...you guessed it.
//point.h class Point { public: int m_coord[ 2 ]; int* m_ptr; Point() { m_coord[ 0 ] = -1; m_coord[ 1 ] = -1; m_ptr = m_coord; } void reset() { m_ptr[ 0 ] = 0; m_ptr[ 1 ] = 0; m_ptr = 0; } };//class Point |
//main.cpp #include <cassert> void main() { Point pt1; Point pt2; draw_segment( pt1, pt2 ); assert( pt1.m_coord[ 0 ] == -1 ); assert( pt1.m_ptr != 0 ); } |
//point.cpp void draw_segment( Point from, Point to ) { // Draw a line: // ... // Then reset objects: from.reset(); // Hmm?.. to.reset(); } |
How are copy constructors defined ?
// point.h class Point { public: Point( Point& other ); // copy constructor 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 ); // Hm... Sometimes, when C++ templates are used, // this could confuse compiler to think that // the last line is a declaration of a function // named pt3, which returns a Point object.