Miscellaneous Topics on Classes
The following topics are covered in this section:
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 |
OUTPUT:
Constructor; 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.
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:
When we have a single argument constructor, the compiler might try to perform an implicit conversion to convert an object into an object of the required type.
This can be prevented by using the keyword ‘explicit’ in the constructor.
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:
All const and reference member data types have to be initialized using an initializer list.
Anything within the body of a constructor is only an assignment and not an initialization.
If one class contains another class (i.e. one object contains an object of another type), then you’ll have to initialize the other object in the initializer list.
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.
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.
The main concepts of OOP are: data abstraction, data encapsulation, inheritance and polymorphism.
Classes form the basis for OOP. Using classes, data and the functions that can operate on the data can be bundled together.
An object is an instance of a class.
The 3 access specfiers used in classes are private, protected and public. These specifiers determine as to who can access the class members.
The constructor is a special function invoked when an object is created and the destructor is invoked when the objected is deleted (or destroyed).
Every class is provided with a default constructor by the compiler if the programmer doesn’t define any constructor.
A constructor without parameters is a default constructor.
Constructors can be overloaded using different parameters.
When creating an array of objects, the class should have a default constructor.
When an object is passed to a function or when an object is returned from a function, a temporary object is created (the temporary object will only invoke the destructor but not the constructor).
Friend functions are not members of a class but they can access the private members of the class.
Objects can be made constant but they can only access constant functions and they cannot alter the value of member data (unless the member is declared to be ‘mutable’).
Static members can be accessed even without using an object.
Copy constructors should be defined in a class in case the dynamic memory allocation operators are used in the constructor and destructor.
Copy constructors are not invoked in assignments.
The ‘explicit’ keyword is used with constructors to prevent implicit conversions.
Initializer lists are used to initialize the member data of an object.
Copyright © 2005 Sethu Subramanian All rights reserved. Sign My Guestbook