Creating objects

Declaring variables

Consider the following variable declarations. int length; int area = 0; DayOfYear today;
What happens "behind the scenes" in our program?
Is that what we want to happen?
Can we control what happens?

Constructor Basics

A constructor is a special kind of member function that is used to create (construct) new objects. A constructor is automatically called whenever an object is created. It is used to initialize the values of some or all data members or perform any other type of initialization that the object requries.

We can add a constructor to our DayOfYear class.

class DayOfYear { public: // Constructor DayOfYear (int initialMonth, int initialDay); void Input( ); void Output( ); // other public methods private: int m_month; int m_day; }; Note that the constructor
  1. Has the same name as the class
  2. Has no return type (not even void)
// we can now declare DayOfYear variables to // create DayOfYear objects like this DayOfYear christmas( 12, 25 ); DayOfYear july4th( 7, 4 ); // or even like this int month = 5; int day = 12; DayOfYear someDay( month, day); The definition of a constructor is given in the same way as any other member function. Once again note that the constructor has the same name as the class and has no return type. DayOfYear::DayOfYear( int initialMonth, int initialDay ) { m_month = initialMonth; m_day = initialDay; } An alternative way of defining constructors is also possible. This method uses the member initialization list. This method is sometimes necessary (we'll see why later) and so is the preferred method, even when not required. DayOfYear::DayOfYear( int initialMonth, int initialDay ) :m_month( initialMonth ), m_day( initialDay ) { // no code } The body of this style of constructor need not be empty, but if left empty, good programming practice requires an explicit comment.

Overloaded constructors

Since a constructor is a member function, a constructor can be (and often is) overloaded. Consider this new version of the DayOfYear class. class DayOfYear { public: // specify the month and day as above DayOfYear (int initialMonth, int initialDay); // initialize to first (day=1) of the month DayOfYear (int initialMonth); // initialize to January 1 // This is the "default" constructor // It is the only one which can be called with no paramters DayOfYear ( ); void Input( ); void Output( ); // other public methods private: int m_month; int m_day; }; //-------------------------------- // DayOfYear constructor // PreConditions: // 1 <= month <= 12 // PostConditions: // new object created with specified // month and day = 1 //------------------------------------ DayOfYear::DayOfYear( int month ) : m_month( month ), m_day( 1 ) { // no code } //-------------------------------- // DayOfYear Default constructor // PreConditions: // none // PostConditions: // new object created with // month = 1 and day = 1 //------------------------------------ DayOfYear::DayOfYear( ) : m_month( 1 ), m_day( 1 ) { // no code }

We can now create DayOfYear objects in a variety of ways

DayOfYear christmas( 12, 25 ); DayOfYear aprilFools ( 4 ); // this declaration uses the default constructor DayOfYear jan1st; // but NOT like this, even though it's // tempting and compiles without errors DayOfYear today( );

Getting a little fancy

Since constructors are functions, not only can they be overloaded as we've seen, they can also have default parameters. In fact, one properly defined constructor using default parameters can take the place of the three constructors defined above. This mechanism gives the user flexibility without requiring the implementer to duplicate code. Consider this new class definition of DayOfYear. class DayOfYear { public: // this constructor can be invoked in three ways // and is also the default constructor DayOfYear (int initialMonth = 1, int initialDay = 1); void Input( ); void Output( ); // other public methods private: int m_month; int m_day; }; Each of these declarations invokes the one and only constructor for the new DayOfYearClass DayOfYear someDay; // month = 1 and day = 1 via default parameters DayOfYear march1st( 3 ); // explicit month = 3; day = 1 by default DayOfYear july4th( 7, 4 ); // explicit month and day

Why haven't we seen this before?

Up until now, we have not provided any constructors for our classes. In this case, the compiler provides a default constructor for our class. This default constructor has been sufficient for our needs so far.

A couple of interesting notes

  1. If you write any constructor for your class, you are no longer provided a default constructor by the compiler.
  2. When declaring a vector of objects such as vector<Bob> group( 20 );, the compiler automatically invokes the default constructor for each object in the vector

What if something goes wrong?

Our DayOfYear constructor has some pre-conditions, but our example code didn't test them....we've been focusing on the concept and syntax.

But what should happen if the DayOfYear object we are asked to create is invalid -- if the month is greater than 12? if the day is negative? if the day is not valid for the specified month (eg Februrary 30th)? In such cases, we need to alert the user that the object is in an invalid state. In more sophisticated situations, the object could be only partially constructed. Such objects are sometimes called "zombies". How do we alert the user to this situation.

For time being, all we can do is to provide the user with a member function -- IsValid( )he can call to ascertain the state of the object. This function could be an accessor for a new private boolean data member (m_isValid) which is set by the constructor, or could decide the validity of the object when called.

Our complete constructor would look something like this

DayOfYear::DayOfYear( int initialMonth, int initialDay ) :m_month( initialMonth ), m_day( initialDay ) { if (m_month < 1 || m_month > 12) m_isValid = false; else if ( m_day < 1 || m_day > 31) m_isValid = false; else if ( day is too big for the specified month) m_isValid = false else m_isValid = true; } In this case, the member function isValid( ) is just an accessor for m_isValid bool DayOfYear::IsValid( ) { return m_isValid; } Now the user can construct DayOfYear objects and check their validity in case there is any doubt DayOfYear futureDay (month, day); if (!futureDay.IsValid( )) { // user decides what to do }

Last Modified: Monday, 28-Aug-2006 10:15:53 EDT