If you do not write a constructor for a class, this is automatically provided for the class.

download the slides used in this presentation
PowerPoint pptx | Acrobat pdf

Objectives

While engaging with this module, you will...

  1. learn how-to initialize your class' member variables
  2. discover the C++ tools for setting up a class for its first use

Constructors for Initializing New Objects

You might be wondering if there is a way to initialize objects that are members of a class that you create. You can initialize a built-in type, so what about a Fraction?

float x = 6.7; or float x(6.7); both of these techniques will do the job you want

To be able to initialize your user-defined types, you must write a special kind of member function for your class. It is called a constructor. Constructors are really rather special in that they have some key features:

  • Constructors are always named the name of the class.
  • Constructors have no return type (this is not void) and hence has no return statement.
  • Constructors can be overloaded like any other function, and you will most likely do so.
  • Constructors are called by the compiler automatically; they are rarely called explicitly by the programmer.
  • If you write no constructor, the compiler will provide your class with a default constructor. The mechanism is suppressed once you write any constructor.

Let�s discuss the details of the last two points. First, the compiler will choose the appropriate constructor for the way in which you declared an object. It will look through the constructors you provide and apply the one that is appropriate. If you wrote none, it will apply the default constructor it creates, but only if it fits. If it is the only option and doesn�t fit, then you will get a compiler error and you need to rewrite your code. Second, the compiler automatically builds a �default� constructor for every class if there is none written by the programmer. That constructor (known as the default) will call the default constructors for each member variable of the class. If they are all primitive C++ types, that means the same thing as if you did not initialize that type of variable � you get the memory and any junk left in it. Very importantly, if you write any constructor, the compiler will not provide the default. That means you should write your own default constructor if you are going to want to declare default objects. Most of the time, you will want that capability. Thus, writing one constructor will usually mean you will write another � the default.

With the Fraction class as it has been defined above, we may do the following

Fraction f;

This declaration will give us a default Fraction object. If we were to print it out, we might get something like (3.5939234e-34/84763874). It�s just the junk that was left in the memory registers.

Now we�ll include a constructor for the Fraction class that will take two arguments, one for the numerator and one for the denominator. Of course, once we do this, we will no longer be provided a default constructor by the compiler.

class Fraction
{
  public:
    Fraction(const int num, const int den);  // constructor takes num and den
    void readin();
    void print();
...
};  // truncated to save space

Now let�s see what happens when we declare objects:

Fraction f(4,7);  // creates fraction with value 4/7
Fraction g;       // NO, won�t compile since there is no constructor that applies

The first declaration will cause the compiler to look for a constructor that takes two int arguments. Finding the constructor we provided, it then builds the object by giving the object�s numerator member the value 4, and the denominator member the value 7. The second declaration will fail because there is no default constructor now. In order to make it work, we must write one. First, let�s write the definition of the constructor we declared.

// in the Fraction.cpp file

Fraction::Fraction(const int num, const int den)  // notice NO return type!
{
    m_Numerator = num;
    m_Denominator = den;
}                                                // notice NO return statement!

Once again, because this is a member function, it has direct access to the private member variables of the class. When f was declare above as a Fraction object, its member variables are given the values of num and den passed in (4 and 7 in our example). f is the calling object, though the constructor is called by the compiler automatically and not explicitly by the coder.

Now, suppose instead of the declaration above, we tried this:

Fraction f(5,0); // numerator 5 and denominator 0 !

With the constructor so provided, this would work. Obviously, there is a problem. We have created code that allows �misbehavior�. We created a back door for corruption. We can prevent this by modifying our constructor definition

Fraction::Fraction(const int num, const int den)
{
    setNumer(num);
    setDenom(den);
}

Hence, we use the gatekeepers we created to safe-keep our private data members. So you see, one member function can call another. If the declaration of the Fraction object is inappropriate, the set function will catch it.

Now let�s investigate alternative methods for defining our constructors. One simple way is to inline the definition much the same way as was done with the get functions.

class Fraction
{
    Fraction(const int num,const int den) {setNumer(num); setDenom(den);}
    ...

Once again, we give away nothing by doing this. The definition is obvious. The payoff is that we have reduced indirection one level, making our code marginally faster. Here is another way, using an initializer list. This is a common method and should be used when appropriate. It is even faster.

class Fraction
{
    Fraction(const int num,const int den) : m_Numerator(num), m_Denominator(den){}
    ...

The syntax requires a colon followed by a common delimited list of the members you wish to initialize. Not all members need be included in that list, but they must be initialized in the manner shown above (using the parentheses, not the = symbol). There must be a body to the constructor, even if it is empty. The above constructor definition is more appropriately done as follows:

class Fraction
{
    Fraction(const int num,const int den) : m_Numerator(num) {setDenom(den);}
    Fraction() : m_Numerator(0),m_Denominator(1){}
    ...

Here I have used the initialize for the numerator since it can be anything, and put the set function for the denominator in the body of the constructor. Notice that I have also included a default constructor. It is a default constructor because it has no parameters, expects no arguments, and sets the numerator and denominator to 0 and 1, respectively, so that the default fraction is 0/1, or just 0. Now, the Fraction object declared as g above will compile and its value is 0.

Let�s consider one more refinement of our constructor for this class.

class Fraction
{
    Fraction(const int n = 0,const int d = 1) : m_Numerator(n) {setDenom(d);}
    ...

Now we have a constructor that has default arguments! It acts as a constructor that will take zero, one, or two arguments.

Fraction f(3), g(4,7), h; // f is 3/1, g is 4/7, and h is 0

One thing you don�t want to do is this:

Fraction f(); // wanting to create a default Fraction

This will work until you try to use the object. The compiler won�t let you use f. It will issue a compiler error. It will confuse you until you realize that the compiler understands the above line of code as a declaration of a function named �f�, that returns a Fraction and has an empty parameter list.

Copy Constructors

There is another kind of constructor you need to be aware of, but won�t need to write this semester. It is called a copy constructor. I will describe them and show how one is used and constructed (no pun � well, yes, it was intended) for the Fraction class.

A copy constructor is used by the compiler when it is necessary to copy an object of the same type. All the same rules apply (no return type, called automatically, etc.). Like the default constructor, a copy constructor is supplied automatically for you by the compiler if you don�t write one. For the purposes of what you will learn this semester in this course, that will suffice. However, once you learn more advanced topics, it might not be good enough for your class. I�ll give a brief explanation of that statement later.

Copy constructors are called in four instances: when an object is created as a copy of an existing object of the same type, when an object is passed to a value parameter, when an object is returned by value by a function, and explicitly. The most common use is when an object is created as a copy of an existing object by the programmer. The code for that would look like this:

Fraction f(3,4);
Fraction g(f);

You see in the second declaration, g is the object to be built and f is the object passed to the constructor.

In general, the desire is for the compiler to copy all the member variables of the source object (f in the example above) to the member variables of the target object (g in the example above). Let�s see how this will come about for the Fraction class.

class Fraction
{
  public:
    Fraction(const Fraction & source);
    ...

�.and in the Fraction.cpp file

Fraction::Fraction(const Fraction & source)
{
    setNumer(source.m_Numerator);
    setDenom(source.m_Denominator);
}

Some details need explanation. Why is the parameter const reference? The const is there to insure that the object being copied is not changed. It shouldn�t be. The parameter is reference because it must be. If it were not reference, it would be pass-by-value. What happens when a function with a pass-by-value parameter is called? The argument passed is copied into that parameter�by calling the copy constructor! Ah, you see now; you create an infinitely recurring call to the copy constructor if you write the definition of the copy constructor incorrectly. Fortunately, our compiler will not let you do this.

Now, why is the compiler generated copy constructor adequate for your code at this point, but won�t be in the future? Since you are unfamiliar with pointers and dynamic memory, the answer is going to be mysterious now, but I�ll state it for the record. If your class contains a pointer, that pointer is copied by the automatic copy constructor. The data that it points to, though, is not copied, and this is what can cause big problems. These problems will be described in detail in the next course. This level of copy is known as �shallow copy�.

What happens if a class doesn't have a constructor?

If we don't define a constructor in a class, then the compiler creates a default constructor(with no arguments) for the class. And if we write a constructor with arguments or no arguments then the compiler does not create a default constructor.

What is automatically provided for a class Java?

The compiler automatically provides a no-argument, default constructor for any class without constructors. This default constructor will call the no-argument constructor of the superclass.

When you write a constructor for a class it still has default constructor that Java automatically provides?

it should use the constructor that requires one Point object argument and one Dimension object argument. When you are writing your own class, you don't have to provide constructors for it. The default constructor, the constructor that takes no arguments, is automatically provided by the runtime system for all classes.

Does Java automatically create a default constructor?

Hence, the Java compiler automatically creates the default constructor. The default constructor initializes any uninitialized instance variables with default values. In the above program, the variables a and b are initialized with default value 0 and false respectively.