- 2.1. Our emphasis
- 2.2. The basic goal—a major difference between C++ and Java
- 2.3. Constructors and destructor
- 2.4. Operator overloading in C++
- 2.5. Operator overloading in Java
- 2.6. Flow-control constructs
- 2.7. Manipulating character strings in C++
- 2.8. Canonical class structure
- 2.9. Overcoming macrophobia
- 2.10. Program readability
- 2.11. Error detection and exceptions
2.3. Constructors and destructor
2.3.1. Purpose
A constructor is a function that is called, either explicitly or by compiler-generated behind-the-scenes code, for the purpose of initializing the state (or member data items) of an object. It is given raw uninitialized memory of the object’s size. It can, of course, explicitly allocate memory for noncontiguous fields, but the compiler considers only the resulting pointer to be part of the actual object.
In both C++ and Java, a constructor is written as a function that has the same name as the class. It returns no value, not even void.
2.3.2. C++ constructors
C++ constructors are invoked in any of five ways:
Declaring objects of the class, with or without initialization parameters:
Complex x(2.5,-1.0), y, z;
Declaring and initializing, using C syntax:
Money price = 49.95;
Explicitly creating an unnamed temporary object:
z = x + Complex(1.0, 1.0);
Implicitly creating an unnamed copy of the object:
Complex fctn(Complex x) // For the parameter {returnexpr;} // and for the result
Implicitly converting:
price += 1.50; // Calls single parameter constructor
C++ constructors, like other functions, can specify default values for optional trailing parameters. This sometimes lets us avoid coding multiple constructors for a class:
class Complex { double rp, ip; public: Complex(const double x=0.0, const double y=0.0) : rp(x), ip(y) {} . .
2.3.3. Java constructors
Java constructors, on the other hand, are always invoked explicitly, as the operand of a new operator, which allocates the memory for the object.
Java doesn’t support default parameter values, but one constructor can invoke another constructor for the same class. To clarify that this is happening, we use the reserved word this instead of the class name:
public class Complex { double rp, ip; public Complex(double x, double y) {rp = x; ip = y;} public Complex(double x) {this(x, 0.0);} public Complex() {this(0.0, 0.0);}
Java’s this keyword does further duty by relieving us from having to think up names for constructor parameters that correspond to member data items. The first constructor above can then be written:
public Complex(double rp, double ip) {this.rp = rp; this.ip = ip;}
2.3.4. Special constructors
In both languages, a constructor with no parameters is called the default constructor and a function that takes a single parameter of the same class is called the copy constructor. In C++, of course, the copy constructor’s parameter must be a reference:
Complex (Complex& x) : rp(x.rp), ip(x.ip) {}
Otherwise, the copy constructor would be invoked recursively to try to pass the parameter by value.
In C++, we sometimes have to specify a default constructor even when we don’t want to give clients the ability to create an object without specifying an initial value. That’s because when we create an array, C++ must create objects to fill it:
Money priceTable[100];
Here, the default constructor for Money will be invoked 100 times to initialize the array. For a numeric class, that’s usually acceptable, since there’s some value we can consider a default, usually zero.2 Note that this doesn’t occur in Java; see Chapter 8.
A constructor with parameters that have a default value is an acceptable default constructor. For example,
Complex(const double x=0.0, const double y=0.0)
2.3.5. C++ compiler-generated functions
Three functions are automatically generated by the compiler whenever the class definition omits them:
- the copy constructor
- the destructor (see Section 2.3.6)
- the assignment operator
These generated versions work just fine for contiguous objects, that is, wherever all the component data items belonging to an object lie inside the object itself. We shall routinely let the compiler generate those default versions since nearly every object used in this book is contiguous (except for the arrays in Chapter 8 and a simple character-string class in Section 2.7). As a courtesy to the future maintenance programmer, however, we customarily affirm in commentary that the omission was not an oversight:
//The compiler will generate an //acceptable copy constructor, //destructor, and assignment operator
2.3.6. C++ destructor
We do need to code an explicit destructor whenever the constructors allocate a resource such as memory. If a constructor allocates memory, the destructor for that class must free it. Furthermore, if any constructor for a class allocates a resource, then all constructors for that class should do so, unless some complicated scheme allows the destructor to figure out when to free the resource.
The destructor has the same name as the class but is prefixed with a tilde character. It takes no parameters since programs never invoke it explicitly:
~Complex(){. . code to free resources...}
The destructor is invoked whenever the object is to be destroyed. That will occur
- when a local object passes out of scope (return from a function, for example)
- when the user program explicitly deletes the object
If we expect our class to be used as a base class in an inheritance hierarchy, then it’s good practice to make the destructor a virtual function, that is, subject to polymorphic invocation:
virtual ~Complex(){ }
That insures that the right destructor will be called if the user program executes
deleteobjPtr; or delete[] objPtr;
where objPtr is declared to be a pointer to the base class but actually contains a pointer to an object of a derived class.
2.3.7. Java destructor and garbage collection
Java has no destructor. Instead, the garbage collector examines the active references to an object and frees the storage when no such reference exists. That eliminates memory management bugs, but it’s still possible to run out of memory if the program leaves long-lived references to data it no longer needs. Suppose there are active references to obj, which in turn contains a reference to an object item that the program no longer needs.
A good-practice solution recommended by Joshua Bloch is to destroy a reference whenever (a) the object it points to is no longer needed and (b) the reference itself (obj) is not about to become free.3
obj = null;
2.3.8. Java assignment operator
The assignment operator exists in Java, but for reference data, it does something entirely different—not only from C++, but from almost every procedural programming language. Java’s assignment operator assigns a reference to the same object. After the program executes
obj1 = obj2; // Reference assignment
any changes to the object referred to (and thought of) as either obj1 or obj2 will be reflected in both. If you want conventional assignment semantics, you have several choices. Suppose obj1 and obj2 are declared as references to instances of class X. Then you can try one of these approaches:
Implement a clone() method in class x to create a new copy of the object and return a reference to it. Class X must also implement the Cloneable interface.
This is a popular Java convention, but unfortunately the returned reference is not to an instance of class X but rather to an instance of the root Object class. The user program has to cast it back to the intended class before using it:
obj1 = (X) obj2.clone();
Implement a method that mimics ordinary assignment semantics:
obj1.set(obj2);
This assumes that obj1 already exists (is not null).
- Just have the client program invoke the copy constructor explicitly:
obj1 = new X(obj2);
This technique works whether obj1 already exists or not.
2.3.9. Implicit conversion in C++
C++ provides two helpful facilities for converting a data item from one type to another without explicit casting, where at least one of the types is not a built-in primitive type. Suppose we’ve declared two objects:
TypeA objA; TypeB objB;
Now suppose the program uses objB in a context that’s invalid for a TypeB object but would make sense for a TypeA object. That will work provided that one but not both of the following have been defined:
a single-parameter constructor in class TypeA that takes a parameter of TypeB:
TypeA::TypeA (const TypeB x);
Of course, the constructor can have more parameters if they have default values.
TypeA::TypeA (const TypeB x,long size=20);
- an inverse conversion operator in class TypeB that creates a TypeA object:
TypeB:: operator TypeA(){.... }
The second approach also works when TypeA is a built-in primitive type, such as double.
Implicit conversion is not transitive. C++ won’t implicitly convert a TypeB object to a TypeC object if you’ve defined a rule for converting a TypeB to a TypeA and another rule for converting a TypeA to a TypeC.
2.3.10. Implicit conversion in Java
Java has no comparable facility, but provides implicit conversion in one special case. If an object reference obj appears in a context in which the compiler expects a character string, the compiler will generate a call to method obj . toString(). That’s handy for simple console output:
System.out.print(obj);
or for concatenating a string with an object:
msg = "Amount due is" + totalPrice;
Of course, the class designer can always provide other conversion methods that client programs will invoke explicitly.