Home / Cplusplus / Destructors In C++

Destructors In C++

Just as a constructor is used to initialize an object when it is created, a destructor is used to clean up the object just before it is destroyed.
 
A destructor always has the same name as the class itself, but is preceded with a ~ symbol.
 
Unlike constructors, a class may have at most one destructor. A destructor never takes any arguments and has no explicit return type.
 
Destructors are generally useful for classes which have pointer data members which point to memory blocks allocated by the class itself. In such cases it is important to release member-allocated memory before the object is destroyed. A destructor can do just that.
 
For example, our revised version of Set uses a dynamically-allocated array for the elems member. This memory should be released by a destructor as follows:
 
class Set {
public:
Set (const int size);
~Set (void) {delete elems;} // destructor
//…….
private:
int *elems; // set elements
int *elems; // set elements
int maxCard; // maximum cardinality
int card; // set cardinality
};
 
Now consider what happens when a Set is defined and used in a function:
 
void Foo (void)
{
Set s(10);
//…
}
 
When Foo is called, the constructor for s is invoked, allocating storage for s.elems and initializing its data members. Next the rest of the body of Foo is executed.
 
Finally, before Foo returns, the destructor for s is invoked, deleting the storage occupied by s.elems.
 
Hence, as far as storage allocation is concerned, s behaves just like an automatic variable of a built-in type, which is created when its scope is entered and destroyed when its scope is left.
 
In general, an object’s constructor is applied just before the object is destroyed. This in turn depends on the object’s scope.
 
For example, a global object is destroyed when program execution is completed; an automatic object is destroyed when its scope is left; and a dynamic object is destroyed when the delete operator is applied to it.
 
Implicit Member Argument
 
When a class member function is called, it receives an implicit argument which denotes the particular object (of the class) for which the function is invoked.
 
For example, in Point pt(10,20);
 
pt.OffsetPt(2,2);
 
pt is an implicit argument to OffsetPt. Within the body of the member function, one can refer to this implicit argument explicitly as this, which denotes a pointer to the object for which the member is invoked. Using this, OffsetPt can be rewritten as:
 
Point::OffsetPt (int x, int y)
{
this->xVal += x; // equivalent to: xVal += x;
this->yVal += y; // equivalent to: yVal += y;
}
 
Use of this in this particular example is redundant. There are, however, programming cases where the use of the this pointer is essential. We will see examples of such cases in Chapter 7, when discussing overloaded operators.
 
The this pointer can be used for referring to member functions in exactly the same way as it is used for data members.
 
It is important to bear in mind, however, that this is defined for use within member functions of a class only. In particular, it is undefined for global functions (including global friend functions).
 
Scope Operator
 
When calling a member function, we usually use an abbreviated syntax. For example:
 
pt.OffsetPt(2,2); // abbreviated form
 
This is equivalent to the full form:
 
pt.Point::OffsetPt(2,2); // full form
 
The full form uses the binary scope operator :: to indicate that OffsetPt is a member of Point.
 
In some situations, using the scope operator is essential. For example, the case where the name of a class member is hidden by a local variable (e.g., member function parameter) can be overcome using the scope operator:
 
class Point {
public:
Point (int x, int y) { Point::x = x; Point::y = y; }
//…..
private:
int xy
}
 
Here x and y in the constructor (inner scope) hide x and y in the class (outer scope). The latter are referred to explicitly as Point::x and Point::y.
 
Member Initialization List
 
There are two ways of initializing the data members of a class. The first approach involves initializing the data members using assignments in the body of a constructor. For example:
 
class Image {
public:
Image (const int w, const int h);
private :
int width;
int height;
//…..
};
 
Image::Image (const int w, const int h)
 
{
width=w;
height=h;
//…..
}
 
The second approach uses a member initialization list in the definition of a constructor. For example:
 
class Image {
public:
Image (const int w, const int h);
private:
int width;
int width; int height;
//…
};
 
Image::Image (const int w, const int h) : width(w), height(h)
 
{
//….
}
 
The effect of this declaration is that width is initialized to w and height is initialized to h. The only difference between this approach and the previous one is that here members are initialized before the body of the constructor is executed.
 
A member initialization list may be used for initializing any data member of a class. It is always placed between the constructor header and body. A colon is used to separate it from the header.
 
It should consist of a comma-separated list of data members whose initial value appears within a pair of brackets.
 
Constant Members
 
A class data member may defined as constant. For example:
 
class Image {
const int width;
const int height;
//……..
};
 
However, data member constants cannot be initialized using the same syntax as for other constants:
 
class Image {
const int width = 256; // illegal initializer!
const int height = 168; // illegal initializer!
//…..
};
 
The correct way to initialize a data member constant is through a member initialization list:
 
class Image {
public:
Image (const int w, const int h);
private:
const int width;
const int height;
//……
};
 
Image::Image (const int w, const int h) : width(w), height(h)
 
{
//…
}
 
As one would expect, no member function is allowed to assign to a constant data member.
 
A constant data member is not appropriate for defining the dimension of an array data member.
 
For example, in
 
class Set {
public:
Set (void) : maxCard(10) { card = 0; }
//…..
private :
const maxcard;
int elems[maxCard]; // illegal!
int card;
};
 
The array elems will be rejected by the compiler for not having a constant dimension. The reason for this being that maxCard is not bound to a value during compilation, but when the program is run and the constructor is invoked.
 
Member functions may also be defined as constant. This is used to specify which member functions of a class may be invoked for a constant object.
 
For Example
 
class set{
public:
Set (void) { card = 0; }
Bool Member (const int) const;
void AddElem (const int);
//…
};
 
Bool Set::Member (const int elem) const
{
//…..
}
 
defines Member as a constant member function. To do so, the keyword const is inserted after the function header, both inside the class and in the function definition.
 
A constant object can only be modified by the constant member functions of the class:
 
const set s;
s.AddElem(10); // illegal: AddElem not a const member
s.Member(10); // ok
 
Given that a constant member function is allowed to be invoked for constant objects, it would be illegal for it to attempt to modify any of the class data members.
 
Constructors and destructors need never be defined as constant members, since they have permission to operate on constant objects. They are also exempted from the above rule and can assign to a data member of a constant object, unless the data member is itself a constant.
 
Static Members
 
A data member of a class can be defined to be static. This ensures that there will be exactly one copy of the member, shared by all objects of the class.
 
For example, consider a Window class which represents windows on a bitmap display:
 
class Window {
static Window *first; // linked-list of all windows
Window *next; // pointer to next window
//……
};
 
Here, no matter how many objects of type Window are defined, there will be only one instance of first.
 
Like other static variables, a static data member is by default initialized to 0. It can be initialized to an arbitrary value in the same scope where the member function definitions appear:
 
Window *Window::first = &myWindow;
 
The alternative is to make such variables global, but this is exactly what static members are intended to avoid; by including the variable in a class, we can ensure that it will be inaccessible to anything outside the class.
 
Member functions can also be defined to be static. Semantically, a static member function is like a global function which is a friend of the class, but inaccessible outside the class.
 
It does not receive an implicit argument and hence cannot refer to this. Static member functions are useful for defining call-back routines whose parameter lists are predetermined and outside the control of the programmer.
 
For example, the Window class might use a call-back function for repainting exposed areas of the window:
 
class window{
//……
static void PaintProc (Event *event); // call-back
};
 
Because static members are shared and do not rely on the this pointer, they are best referred to using the class::member syntax.
 
For example, first and PaintProc would be referred to as Window::first and Window::PaintProc. Public static members can be referred to using this syntax by nonmember functions (e.g., global functions).
 
Member Pointers
 
As we have previously explained you how a function pointer is used to pass the address of a comparison function to a search function.
 
It is possible to obtain and manipulate the address of a member function of a class in a similar fashion. As before, the idea is to make a function more flexible by making it independent of another function.
 
The syntax for defining a pointer to a member function is slightly more complicated, since the class name must also be included in the function pointer type.
 
For example:
 
typedef int (Table::*Compare)(const char*, const char*);
 
defines a member function pointer type called Compare for a class called Table.
 
This type will match the address of any member function of Table which takes two constant character pointers and returns an int. Compare may be used for passing a pointer to a Search member of Table:
 
class table {
public :
Table (const int slots);
int Search (char *item, Compare comp);
int CaseSesitiveComp (const char*, const char*);
int NormalizedComp (const char*, const char*);
private:
int slots ;
char** entities ;
};
 
The definition of Table includes two sample comparison member functions which can be passed to Search. Search has to use a slightly complicated syntax for invoking the comparison function via comp:
 
int Table::Search (char *item, Compare comp)
{
int bot=0;
int top = slots – 1;
int mid, cmp;
 
while (bot <= top) {
mid = (bot + top) / 2;
if ((cmp = (this->*comp)(item, entries[mid])) == 0)
return mid; // return item index
else if (cmp < 0)
top = mid – 1; // restrict search to lower half
else
bot = mid + 1; // restrict search to upper half
}
return -1; // not found
}
 
Note that comp can only be invoked via a Table object (the this pointer is used in this case). None of the following attempts, though seemingly reasonable, will work:
 
(*comp)(item, entries[mid]); // illegal: no class object!
 
(Table::*comp)(item, entries[mid]); // illegal: no class object!
this->*comp(item, entries[mid]); // illegal: need brackets!
 
The last attempt will be interpreted as:
 
this->*(comp(item, entries[mid])); // unintended precedence!
 
Therefore the brackets around this->*comp are necessary. Using a Table object instead of this will require the following syntax:
 
Table tab(10);
(tab.*comp)(item, entries[mid])
 
Search can be called and passed either of the two comparison member functions of Table.
 
For example:
 
tab.Search(“Sydney”, Table::NormalizedComp);
 
The address of a data member can be obtained using the same syntax as for a member function.
 
For example:
 
int Table::*n = &Table::slots;
int m = this->*n;
int p = tab.*n;
 
The above class member pointer syntax applies to all members except for static. Static members are essentially global entities whose scope has been limited to a class. Pointers to static members use the conventional syntax of global entities.
 
In general, the same protection rules apply as before: to take the address of a class member (data or function) one should have access to it. For example, a function which does not have access to the private members of a class cannot take the address of any of those members.
 
References Members
 
A class data member may defined as reference. For example:
 
class Image {
int width;
int height;
int &widthRef;
//……
};
 
As with data member constants, a data member reference cannot be initialized using the same syntax as for other references:
 
class Image {
public :
Image (const int w, const int h);
private :
int width ;
int height ;
int & width Ref ;
//….
};
 
Image::Image (const int w, const int h) : widthRef(width)
 
{
//….
}
 
This causes widthRef to be a reference for width.
 
Class Object Members
 
A data member of a class may be of a user-defined type, that is, an object of another class. For example, a Rectangle class may be defined using two Point data members which represent the top-left and bottom-right corners of the rectangle:
 
class Rectangle{
public :
Rectangle (int left, int top, int right, int bottom);
//….
private :
Point top left ;
Point bot right;
};
 
The constructor for Rectangle should also initialize the two object members of the class. Assuming that Point has a constructor, this is done by including topLeft and botRight in the member initialization list of the constructor for Rectangle:
 
Rectangle::Rectangle (int left, int top, int right, int bottom)
: topLeft(left,top), botRight(right,bottom)
{
}
 
If the constructor for Point takes no parameters, or if it has default arguments for all of its parameters, then the above member initialization list may be omitted. Of course, the constructor is still implicitly called.
 
The order of initialization is always as follows. First, the constructor for topLeft is invoked, followed by the constructor for botRight, and finally the constructor for Rectangle itself.
 
Object destruction always follows the opposite direction. First the destructor for Rectangle (if any) is invoked, followed by the destructor for botRight, and finally for topLeft.
 
The reason that topLeft is initialized before botRight is not that it appears first in the member initialization list, but because it appears before botRight in the class itself.
 
Therefore, defining the constructor as follows would not change the initialization (or destruction) order:
 
Rectangle::Rectangle (int left, int top, int right, int bottom)
: botRight(right,bottom), topLeft(left,top)
{
}
 
Destructor is use to delete all the dynamic allocate memory.
 
For example
 
//this is the destructor for a ragged array with a pointer to staff and staff have a pointer //to a char. First delete pointer to char, second delete pointer to staff, then delete node
 
//destructor codes
ragged::~ragged()
{
node*ptr;
 
//while not end of the ragged array while(head!=NULL) //delete all the data
 
{
ptr=head->next;
delete head->staff->string;
delete head->staff;
delete head;
head=ptr;
}
}