Miscellaneous Topics on Classes


The following topics are covered in this section:


Returning Objects revisited (return value optimization):

Returning an unnamed object (instead of a named object) might help the compiler to optimize the code to improve efficiency.

Method I (copy constructor invoked) Method II Returning an unnamed object
class base
{
private:
  int val;
public:
base(int x=0)
{
  val=x;
  cout<<endl<<"Constructor;value:"<<val;
}
 ~base( )
{
  cout<<endl<<"Destructor; value:"<<val;
}
base ret_base( )
{
  base btemp(val+1);
  return btemp;
}
base(const base &rc)
{
  cout<<endl<<"Copy constructor"; val=rc.val;
}
};
int main( )
{
  base b1(5);
  b1.ret_base( );
  return 0;
}
class base
{
private:
   int val;
public:
base(int x=0)
{
   val=x;
   cout<<endl<<"Constructor;value:"<<val;
}
~base( )
{
   cout<<endl<<"Destructor;value:"<<val;
}
base ret_base( )
{
   return base(val+1); //unnamed object
}
base(const base &rc)
{
   cout<<endl<<"Copy constructor"; val=rc.val;
}
};
int main( )
{
   base b1(5);
   b1.ret_base( );
   return 0;
}
OUTPUT:

Constructor; value:5
Constructor; value:6
Copy constructor
Destructor; value:6
Destructor; value:6
Destructor; value:5

OUTPUT:

Constructor; value:5
Constructor; value:6
Destructor; value:6
Destructor; value:5

In the first method we create an object in the function and return it:

base btemp(val+1);
return btemp;

In this case, a constructor and destructor are called for objects ‘b1’ and ‘btemp’. In addition a copy constructor and destructor are called on a temporary object when we return an object from the function:

return btemp;

In the second method we return an unnamed object directly:

return base(val+1);

Here neither is an additional destructor called nor is the copy constructor invoked. This is called return value optimization (wherein the compiler is able to avoid the creation of a temporary object; thus improving efficiency).

Note: The compiler tries its best to optimize code using various techniques (so that it can reduce work for the processor which in turn helps improve efficiency of our program). Return value optimization is one such optimization technique.


The keyword ‘explicit’

Let’s consider a simple program using classes. The class ‘circle’ has 2 constructors: a single argument constructor and a no argument constructor.

#include <iostream.h>

class circle
{
private:
    int radius;

public:
circle( )
{
    cout<<"No argument constructor invoked";
    radius=0;
}

circle(int x)
{
    cout<<"Single argument constructor invoked";
    cout<<"is:"<<x;
    radius=x;
}

int get_radius( )
{
    return radius;
}

};

void display_area(circle c1)
{
    int r=c1.get_radius( );
    cout<<"circle has an area:"<<(3.14*r*r);
}

int main( )
{
    display_area(10);
    return 0;
}

The output will be:

Single argument constructor invoked
Radius is:10
The circle has an area:314

The problem that can be caused is quite obvious. We used a statement:

display_area(10);

but the actual function is declared as:

    void display_area(circle c1)

The display_area( ) function is supposed to take a circle object as its argument whereas it seems to be accepting even an integer. How is this possible?

When the compiler encounters:

display_area(10);

it checks up and realizes that display_area( ) needs a circle object. So it goes to the class ‘circle’ to see if it can perform some conversion (i.e. if it can convert an integer into a circle object). The compiler finds a single argument constructor:

circle(int x)

and decides to use this constructor to convert the 10 into circle c1(10). This is called an implicit conversion (i.e. the compiler does the conversion without prompting the user- it considers this a normal scenario). If we didn’t have a single-argument constructor, the compiler would not permit this. For example if our constructor were:

circle(int x, int y)

instead of

circle(int x)

then the compiler would give an error message saying “conversion from `int' to non-scalar type `circle' requested”

So, what should we do to prevent this implicit conversion from taking place? Should we avoid using a single argument constructor? C++ provides a keyword called ‘explicit’.

Instead of the constructor:

circle(int x)
{
cout<<"Single argument constructor invoked";
cout<<"is:"<<x;
radius=x;
}

add the keyword ‘explicit’ as shown below:

explicit circle(int x)
{
cout<<"Single argument constructor invoked";
cout<<"is:"<<x;
radius=x;
}

and now try to compile the program. You will get a compilation error pointing to the function call:

display_area(10);

along with the error message “conversion from `int' to non-scalar type `circle' requested”

To summarize this section:


Constructor using initializer list:

The following code:

int main( )
{
const int x;
return 0;
}

will not compile because a ‘const’ has to be initialized. The compiler error will be:

‘x’ : const object must be initialized if not extern

The next code snippet will also produce a compiler error:

int main( )
{
int x;
int &ref;
return 0;
}

This time we have created a reference variable but haven’t initialized it. The error message will be:

'ref' : references must be initialized

Keeping these two facts in mind, let’s go back to initializer lists. The constructor for the class can be written as:

counter ( )
{
count=7;
}

or we could use the form:

counter ( ) : count (7)
{ }

The second method makes use of an initializer list (i.e. count(7) forms the initializer list over here) to initialize the values while the constructor body is empty. The first form isn’t really an initialization; it’s just an assignment.

The first form is equivalent to writing:

int count;
count = 7;

Thus the variable ‘count’ is first created and later assigned a value of 7. Initialization means giving a value to the variable at the time of creation; i.e.

    int count=7;

is an initialization.

To prove that

counter ( )
{
count=7;
}

is just an assignment, let’s write a small program.

#include <iostream.h>

class counter
{
private:
const int count;

public:
counter( )
{
count=0;
}

};

int main( )
{
return 0;
}

Compiling this code you would get at least a couple of errors:

'count' : must be initialized in constructor base/member initializer list
l-value specifies const object

The second error is produced because of the line:

    count=0; //A const object cannot be assigned a value.

Or you might get the error: assignment of read-only member `counter::count'

This program clearly indicates that the constructor we’ve used is simply performing an assignment operation rather than initialization of member data. Let’s modify the code:

#include <iostream.h>

class counter
{
private:
    const int count;

public:
counter( ):count(0)
{}

void display( )
{
    cout<<"count is:"<<count;
}

};

int main( )
{
counter c1;
c1.display( );
return 0;
}

This code will compile and execute correctly.

Thus a constructor is of the general form:

class-name(arguments) : member(value), member(value)…
{
//body of constructor for any assignments
}

To summarize:

Beware: When using an initialization list the order in which the members are initialized depends on the order in which you declare them within the class (does not depend on the order specified in the initializer list). The first member in the class will be the first member to be initialized. If your initializations depends on the sequence then be careful while using initializer lists.


Declaration and Definition revisited:

Now we are in a better position to understand these 2 terms (which were introduced in the first chapter itself).

Declaration:

We tell the compiler that “this is the name of an object of this type which I might use later in my program.” Example:

class virus; //class declaration

We tell the compiler beforehand that ‘virus’ is a class.

int getHealth ( ); //function declaration

          extern int error_no; //declaration of error_no

The last example is used when you are writing code in multiple files (where one part of the code uses variables defined in another file). This will be dealt with later.

But the point to note is that in the above 3 cases, we are only declaring something to the compiler (a function, a class and an object). The compiler does not allocate any memory space for them.

Definition:

Let’s start of with the examples:

//Defining a class
class virus
{
private:
    int life;

//rest of the class
};

//defining a function
int getHealth ( )
{
return health;
}

In a definition we provide the compiler with details. What about the next case?

//defining and declaring
int error_no;

This is what we’ve been using frequently so far. This statement defines error_no (the compiler allocates storage space for it) and also declares error_no as type integer.


Recap


Go back to the Contents Page


Copyright © 2005 Sethu Subramanian All rights reserved. Sign My Guestbook