When declaring a destructor which character is used?

Lesson 4 What is a destructor?
Objective Explore the use of destructor member functions.

A destructor is a member function whose name is the class name preceded by the tilde character (~). A destructor's usual purpose is to destroy values of the class type, typically by using delete. Destructors

  1. are invoked implicitly when an object goes out of scope. This is typically at block exit for automatic objects and program exit for static objects.
  2. cannot be overloaded or take arguments.


Destructors are usually used to deallocate memory and do other cleanup for a class object and its class members when the object is destroyed. A destructor is called for a class object when that object passes out of scope or is explicitly deleted.
A destructor is a member function with the same name as its class prefixed by a ~ (tilde). For example:


class X { public: // Constructor for class X X(); // Destructor for class X ~X(); };
A destructor takes no arguments and has no return type. Its address cannot be taken. Destructors cannot be declared const, volatile, const volatile or static. A destructor can be declared virtual or pure virtual. If no user-defined destructor exists for a class and one is needed, the compiler implicitly declares a destructor. This implicitly declared destructor is an inline public member of its class.

The complement of the constructor is the destructor. In many circumstances, an object will need to perform some action or actions when it is destroyed. Local objects are created when their block is entered, and destroyed when the block is left. Global objects are destroyed when the program terminates. When an object is destroyed, its destructor (if it has one) is automatically called. There are many reasons why a destructor may be needed. For example, an object may need to deallocate memory that it had previously allocated or it may need to close a file that it had opened. In C++, it is the destructor that handles deactivation events. The destructor has the same name as the constructor, but it is preceded by a ~. For example, here is the stack class and its constructor and destructor.

(Keep in mind that the stack class does not require a destructor; the one shown here is just for illustration.)


// This creates the class stack. class stack { int stck[SIZE]; int tos; public: stack(); // constructor ~stack(); // destructor void push(int i); int pop(); }; // stack's constructor stack::stack() { tos = 0; cout << "Stack Initialized\n"; } // stack's destructor stack::~stack() { cout << "Stack Destroyed\n"; }

Page 2

Lesson 5 Constructor and destructor example
Objective Class that uses simple constructor/simple destructor.
class ch_stack { public: ch_stack(int size) //constructor { s = new char[size]; assert (s); } ~ch_stack() { delete []s; } //destructor . . . private: enum { EMPTY = -1 }; char* s; int max_len; int top; };

Constructors and destructors do not have return types, and cannot use return (expression) statements.
For the rest of this module, we will look at constructors. Destructors will be covered in more detail later.

As an example, we will create a class of objects that might be useful as a general-purpose programming element. A counter is a variable that counts things. Maybe it counts file accesses, or the number of times the user presses the Enter key, or the number of customers entering a bank. Each time such an event takes place, the counter is incremented (1 is added to it). The counter can also be accessed to find the current count. Let us assume that this counter is important in the program and must be accessed by many different functions. In procedural languages such as C, a counter would probably be implemented as a global variable. Global variables complicate the program's design and may be modified accidentally. This example, COUNTER, provides a counter variable that can be modified only through its member functions.
// counter.cpp // object represents a counter variable #include <iostream> using namespace std; //////////////////////////////////////////////////////////////// class Counter { private: unsigned int count; //count public: Counter() : count(0) //constructor{ /*empty body*/ } void inc_count(){ count++; } int get_count(){ return count; } }; ////////////////////////////////////////////////////////////////
int main() { Counter c1, c2; //define and initialize cout << “\nc1=” << c1.get_count(); //display cout << “\nc2=” << c2.get_count(); c1.inc_count(); //increment c1 c2.inc_count(); //increment c2 c2.inc_count(); //increment c2 cout << “\nc1=” << c1.get_count(); //display again cout << “\nc2=” << c2.get_count(); cout << endl; return 0; } The Counter class has one data member: count, of type unsigned int (since the count is always positive). It has three member functions: the constructor Counter(), inc_count(), which adds 1 to count; and get_count(), which returns the current value of count.

Page 3

Lesson 6 An initialization constructor
Objective Use constructor to initialize values of class data members

When an object of type Counter is first created, we want its count to be initialized to 0. After all, most counts start at 0. We could provide a set_count() function to do this and call it with an argument of 0, or we could provide a zero_count() function, which would always set count to 0. However, such functions would need to be executed every time we created a Counter object.
Counter c1; //every time we do this, c1.zero_count(); //we must do this too This is mistake prone, because the programmer may forget to initialize the object after creating it. It is more reliable and convenient, especially when there are a great many objects of a given class, to cause each object to initialize itself when it is created. In the Counter class (from the previous lesson), the constructor Counter() does this. This function is called automatically whenever a new object of type Counter is created. Thus in main() the statement Counter c1, c2; creates two objects of type Counter. As each is created, its constructor, Counter(), is executed. This function sets the count variable to 0.

So the effect of this single statement is to not only create two objects, but also to initialize their count variables to 0.


Use constructor to initialize values of class data members
We will now examine the implementation of a data type mod_int to store numbers that are computed with a modulus.


//Modulo numbers and constructor initialization class mod_int { public: mod_int(int i) { v = i % modulus; } void assign(int i) { v = i % modulus; } void print() const { cout << v << '\t'; } const static int modulus; private: int v; }; const int mod_int::modulus = 60;

The integer v is restricted in value to 0, 1, 2, ... , modulus - 1. It is the programmer's responsibility to enforce this restriction by having all member functions guarantee this behavior.
The member function mod_int::mod_int(int) is a constructor. It does not have a return type. It is invoked when objects of type mod_int are used in a definition and has one argument.
When invoked, it requires an expression that is assignment-compatible with its int parameter. It then creates and initializes the declared object.


There are some unusual aspects of constructor functions. First, it is no accident that they have exactly the same name (Counter in this example) as the class of which they are members. This is one way the compiler knows they are constructors. Second, no return type is used for constructors.

Why not? Since the constructor is called automatically by the system, there is no program for it to return anything to; a return value would not make sense. This is the second way the compiler knows they are constructors.



Page 4

Lesson 7Using a constructor
Objective Use a constructor to create an object.

We can declare objects of type mod_int by using the mod_int constructor. Here are two examples:


mod_int a(0); // a.v = 0; mod_int b(61); // b.v = 1;

We could not, however, declare an object of type mod_int without providing a parameter. The statement:


mod_int a;

would be incorrect.
Since the mod_int class has only the one constructor of argument list int, a mod_int declaration must have an integral expression passed as an initializing value. Note that by not allowing a mod_int object to be declared without an initializing expression, we prevent runtime errors due to uninitialized objects.
Using the mod_int type, we can write code to convert seconds into minutes and seconds.
See the constructor example


Only fairly simple classes can rely on the synthesized default constructor. The most common reason that a class must define its own default constructor is that the compiler generates the default for us only if we do not define any other constructors for the class. If we define any constructors, the class will not have a default constructor unless we define that constructor ourselves. The basis for this rule is that if a class requires control to initialize an object in one case, then the class is likely to require control in all cases. The compiler generates a default constructor automatically only if a class declares no constructors. A second reason to define the default constructor is that for some classes, the synthesized default constructor does the wrong thing. Remember that objects of builtin or compound type (such as arrays and pointers) that are defined inside a block have undefined value when they are default initialized The same rule applies to members of built-in type that are default initialized. Therefore, classes that have members of built-in or compound type should ordinarily either initialize those members inside the class or define their own version of the default constructor. Otherwise, users could create objects with members that have undefined value.

Warning: Classes that have members of built-in or compound type usually should rely on the synthesized default constructor only if all such members have in-class initializers.


A third reason that some classes must define their own default constructor is that sometimes the compiler is unable to synthesize one.
For example, if a class has a member that has a class type, and that class does not have a default constructor, then the compiler cannot initialize that member. For such classes, we must define our own version of the default constructor. Otherwise, the class will not have a usable default constructor. We will see additional circumstances that prevent the compiler from generating an appropriate default constructor



Page 5

Lesson 8 The default constructor
Objective Get familiar with creating default constructors.

A constructor with a void argument list or a list whose arguments all have defaults is called the default constructor. A default constructor has the special purpose of initializing arrays of objects of its class. It is often convenient to overload the constructor with several function declarations. In our mod_int example from the last lesson, it could be desirable to have the default value of v be 0. By adding the default constructor


mod_int() { v = 0; }
as a member function of mod_int, it is possible to have the following declarations:
mod_int s1, s2; // init private member v to 0 mod_int d[5]; // arrays properly initialized

In both of these declarations, the empty parameter list constructor is invoked.
If a class does not have a constructor, the system provides a default constructor. If a class has constructors, but does not have a default constructor, then array allocation causes a syntactic error.


A default constructor is one that can be called with no arguments. A default constructor is used to create a "generic object", but it is also important when the compiler is told to create an object but is not given any details. For example, if you take the struct Y defined previously and use it in a definition like this,

Y y2[2] = { Y(1) }; the compiler will complain that it cannot find a default constructor. The second object in the array wants to be created with no arguments, and that is where the compiler looks for a default constructor. In fact, if you simply define an array of Y objects,
Y y3[7]; the compiler will complain because it must have a default constructor to initialize every object in the array.
The same problem occurs if you create an individual object like this: Y y4; Remember, if you have a constructor, the compiler ensures that construction always happens, regardless of the situation. The default constructor is so important that if (and only if) there are no constructors for a structure (struct or class), the compiler will automatically create one for you. So this works:
// Automatically-generated default constructor class V { int i; // private }; // No constructor int main() { V v, v2[10]; } ///:~

If any constructors are defined, however, and there is no default constructor, the instances of V above will generate compile-time errors. You might think that the compiler-synthesized constructor should do some intelligent initialization, like setting all the memory for the object to zero. But it does not, since that would add extra overhead but be out of the programmer's control.

If you want the memory to be initialized to zero, you must do it yourself by writing the default constructor explicitly. Although the compiler will create a default constructor for you, the behavior of the compiler-synthesized constructor is rarely what you want. You should treat this feature as a safety net, but use it sparingly. In general, you should define your constructors explicitly and not allow the compiler to do it for you.



Page 6

Lesson 9 Constructor initializers
Objective Add a constructor, an initializer list, and a destructor

Add a constructor, an initializer list, and a destructor to the person class you created earlier. We can also use a constructor to initialize sub-elements of an object. Constructor initializers are specified in a comma-separated list that follows the constructor parameter list and is preceded by a colon. An initializer's form is member name (expression list)
For example:


foo::foo(int* t):i(7), x(9.8), z(t) //initializer list { //other executable code follows here ....}
We will now recode the mod_int constructor so it initializes the class member v. Instead of coding the constructor like this:
mod_int::mod_int(int i = 0) { v = i % modulus; } we can code it using an initializer list like this: //Default constructor for mod_int mod_int::mod_int(int i = 0) : v(i % modulus){}

Notice how initialization replaced assignment. In general, initialization is preferred to assignment.
It is not always possible to assign values to members in the body of the constructor. An initializer list is required, however, when a nonstatic member is either a const or a reference. When members are themselves classes with constructors, the expression list is matched to the appropriate constructor signature to invoke the correct overloaded constructor.


When declaring a destructor which character is used?
Syntax 4.9 - Constructor with Field Initializer List
Purpose: Supply the implementation of a constructor, initializing data fields before the body of the constructor.

Page 7

Lesson 10 Constructors as Conversions
Objective Constructors can perform automatic conversions.

Constructors with a single parameter are automatically conversion functions. One reason C++ has implicit conversions for ADTs is that, according to OOP conventions, ADTs should have the look and feel of the native types.
Consider the following class, whose purpose is to print nonvisible characters with their ASCII designation; for example, the code 07 (octal) is alarm or bel.


//ASCII printable characters class pr_char { public: pr_char(int i = 0) : c(i % 128) {} void print() const { cout << rep[c]; } private: int c; static char* rep[128]; }; char* pr_char::rep[128] = { "nul", "soh", "stx", ..... "w", "x", "y", "z",""{", "|", "}", "~", "del" }; int main() { int i; pr_char c; for (i = 0; i < 128; ++i) { c = i; //or: c = static_cast<pr_char>(i); c.print(); cout << endl; } }

It is very important to understand precisely how arguments are passed to a function. This affects how you write functions and ultimately how they operate. There are also a number of pitfalls to be avoided. In general, the function arguments should correspond in type and sequence with the list of parameters in the function definition. You have no latitude so far as the sequence is concerned, but you do have some flexibility in the argument types. If you specify a function argument of a type that does not correspond to the parameter type, then the compiler inserts an implicit conversion of the argument to the type of the parameter where possible.
The rules for automatic conversions of this kind are the same as those for automatic conversions in an assignment statement. If an automatic conversion is not possible, you will get an error message from the compiler. There are two mechanisms by which arguments are passed to functions, pass-by-value and pass-by-reference.


The constructor creates an automatic conversion from integers to pr_char. Notice, the c = i; statement in the loop implies this conversion. It is also possible to explicitly use a cast.
Unneeded conversions produced by conversion constructors are a source of obscure runtime bugs. Avoid this problem by using the keyword explicit to preface a single-argument constructor. This disables automatic conversion.



Page 8

Lesson 11

In this module you learned how to use constructor member functions to create objects from classes. In particular, you learned:

  1. How constructors and destructors provide management of class-defined objects
  2. How to use a constructor for initialization
  3. About the use of default constructors
  4. How to use constructor initializers
  5. How constructors can perform automatic conversions


Objects generally need to initialize variables or assign dynamic memory during their process of creation to become operative and to avoid returning unexpected values during their execution. A class can include a special function called a constructor, which is automatically called whenever a new object of this class is created. This constructor function must have the same name as the class, and cannot have any return type.

Wouldn’t it be nice to be able to initialize a rational object with a numerator and denominator, and have them properly reduced automatically? You can do that by writing a special member function that looks and acts a little like assign, except the name is the same as the name of the type (rational), and the function has no return type or return value. Listing 4-11 shows how to write this special member function.

Listing 4-11

#include <cassert> #include <cstdlib> #include <iostream> /* Compute GCD of two integers, using Euclid’s algorithm. */ int gcd(int n, int m) { n = std::abs(n); while (m != 0) { int tmp(n % m); n = m; m = tmp; } return n; } /// Represent rational number. struct rational { /// Construct rational object, given numerator and denominator. /// Always reduce to normal form. /// @param num numerator /// @param den denominator /// @pre denominator > 0 rational(int num, int den) : numerator{num}, denominator{den} { reduce(); }
/// Assign numerator and denominator, then reduce to normal form. /// @param num numerator /// @param den denominator /// @pre denominator > 0 void assign(int num, int den) { numerator = num; denominator = den; reduce(); }
/// Reduce the numerator and denominator by their GCD. void reduce() { assert(denominator != 0); int div{gcd(numerator, denominator)}; numerator = numerator / div; denominator = denominator / div; } int numerator; /// < numerator gets the sign of the rational value int denominator; ///< denominator is always positive };
int main() { rational pi{1420, 452}; std::cout << "pi is about " << pi.numerator << "/" << pi.denominator << '\n'; }

Click the Quiz link below to take a multiple-choice quiz covering the topics presented in this module.
C++ Constructor - Quiz