Constructors and Destructors

An object will often need to be put into some initial state. The set() method does this for Point objects, as does make(). This does fine for simple classes like Point, but more complex classes need to set themselves up properly for business. You cannot safely use such an object without initializing it, and it is easy to forget to explicitly initialize an object. Constructors are special methods that will automatically be called when an object is declared.

Objects often allocate memory dynamically and, therefore, require destruction as well as construction: Any pointer allocated with new must be given back to the system with delete, etc. Destructors are methods that will be called when the life of an object is over.

Class Constructors

Local variables will often not contain sensible values after declaration (unless they were explicitly initialized, of course). This is because non-static locals are genuinely temporary; some space is made on the stack for them, and when the function ends, that space is taken back by popping the stack. That space can then be used for somebody else's local variables, so local variables will contain whatever the last stack value was. Unlike Java, C++ does not implicitly initialize ordinary variables; people may not need it, and the motto of C++ is “you never pay for what you don't use.” However, you can specify code that can be automatically called whenever an object is created. These special functions are called constructors. A class constructor has the same name as the class (or struct), and no return type; otherwise, it behaves like any other method. Here is a constructor for the Point example (the full definition of Point is available from chap7points.h):

struct Point {
  int x,y;
  void set(int xp, int yp) {
      x = xp;  y = yp;
  }

  Point(int xp, int yp) {
     set(xp,yp);
  }
};
;> Point p(100,120);
;> p.x; p.y;
(int) 100
(int) 120

Previously, we defined non-member functions such as make_point() or member functions like set() to do the important job of initializing an object to a known value, but constructors are easier to use and often more efficient (because make_point() relies on copying the structure). Constructors are also called when an object is created dynamically, as in the following example:

;> Point *ppoint = new Point(40,23);
;> ppoint->x;
(int) 40

Default Constructors

With the constructor shown in the preceding section, you cannot declare a Point without supplying initial values. The error message is instructive: The compiler tells you that no constructor of Point can match a signature that has no arguments. Here is what happens:


;> Point pp;
CON 4:Function cannot match 0 parameters
CON 4:no default constructor
;> Point p();
;> p.x;
CON 12:Not a class object or pointer

Curiously, you can get away with using Point p(), but the result is not what you expect. The compiler considers this to be a forward declaration of the function p(), which returns a Point value and takes no arguments—hence the error message. Watch out for this because it might seem logical to declare a Point like this, but C++ (like English) is not necessarily logical.

The constructor with no arguments is called the default constructor, and it is used when the object is declared without arguments. Constructors, like any other functions, can be overloaded, so you can add another constructor, this time taking no arguments:

struct Point {
  ...// same as before
  Point() {
     set(0,0);
  }
};

It is now possible to declare Point p, which results in a Point object that is always properly initialized.

A class can contain objects that need construction. If you supply a constructor, C++ guarantees that the objects in the class will also be constructed. If you do not supply a constructor, the compiler will generate a default constructor that will do this.

The question now is “How could the original definition of Point have worked?” What happens for plain old structures? If you do not supply a constructor for a plain structure, C++ assumes that there is no need for construction, and it doesn't bother to find the default constructor. If the class contains objects (such as strings) that need construction, the compiler generates a default constructor, which guarantees that the objects are constructed. Unlike other object-oriented languages, C++ is usually pretty good at handling that kind of detail for you. For example, an (apparently) simple class that contains some standard string objects automatically acquires a default constructor. Any string objects will be initialized to the empty string (""). You need to be aware of this because occasionally you will forget to supply a default constructor, and a straightforward class will refuse to compile.

Why doesn't the system supply a default constructor automatically for Point? As soon as you define any constructor, C++ assumes that you want to take full responsibility for constructing that object. It assumes that you are doing some special initialization, and it doesn't want to second-guess your needs.

Explicit Clean-up

Objects often grab system resources, such as memory or file handles. A tiresome task in traditional programming is remembering to give these resources back to the system. If we didn't take our library books back, eventually the library would run out of books. (Java has a different philosophy, called garbage collection: Occasionally the librarians let themselves into your house and pick up your library books.) If you allocate memory with the operator new, at some point you must use delete; if you open a file for writ ing, then you must close it. Failure to close files might mean that data isn't written to the file; with the iostream library, you are writing to a buffer that is flushed to the file when it's properly closed. (Windows puts some further buffering between the file system and the actual disk.) But I have told you not to worry about explicit closing of files. How is this file-closing achieved automatically?

You can define class destructors, which are designed to clean up and “unconstruct” objects. Destructors do not have return types, the same as constructors, and they use the tilde symbol (~). For example, this is (roughly) how ofstream closes a file:

class ofstream ... {
 ...
 ~ofstream() {
    close();
  }
};

A class's destructor is very rarely called directly. Usually it is automatically called at the end of a variable's career. If an ofstream object is declared in a function, the destructor guarantees that the file will be closed, however the function ends. You can jump out of the function by using return at any point, or if an exception is thrown; in either case the destructor will be called.

For example, you can add to the Point class a destructor that does nothing but announce the object's destruction. The following code then defines a function test_destructor() that shows the three main ways of leaving a function: returning from some arbitrary point, leaving when an exception terminates the function's execution, and exiting normally. test_destructor() is exercised by catch_it(), which will catch any exceptions thrown:


struct Point {
  ...
  ~Point() {
    cout << "Destroyed " << x << ' ' << y << endl;
   }
 };

 void test_destructor(int how) {
   Point p;
   switch(how) {
   case 0: return;
   case 1: throw "exception";
   default: break;
   }
   cout << "normal exit
";
 }

 void catch_it() {
    try {
      test_destructor(1);
    }  catch(...) {
      cout << "caught!
";
    }
 }

;> test_destructor(2);
normal exit
Destroyed 0 0
;> test_destructor(0);
Destroyed 0 0
;> catch_it();
Destroyed 0 0
caught!

Just as C++ guarantees that members of a class (or struct) will be properly constructed, it guarantees that they will be destroyed if they have destructors. If necessary, the compiler can generate a destructor. In the next example, you define the structure Line as consisting of a start and an endpoint, which are both of type Point. Rather than defining a function, you can put declarations within a block when in interactive UnderC mode. The Line object lin is a local variable that goes out of scope after the close brace; you then see that the points were properly destroyed. That is,

struct Line {
  Point start;
  Point end;
};
;> {
							Line lin; }
Destroyed 0 0
Destroyed 0 0

The delete operator also causes the destructor to be called. In this case, the programmer completely controls the lifetime of the object:

;> Point *pp = new Point(3,4);
;> delete pp;
Destroyed 3 4

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.36.194