Section 1 - Implications of Constructor Overloading
5A.1.1 Orientation
This week we cover two advanced C++ topics. In the first module, we'll discuss overloading, especially as it relates to operators. (Operator overloading is a feature you won't find in Java.) In the second module, we'll focus on exceptions and try/catch blocks. This will enable us to flag errors in constructors, methods that do not have return values.
5A.1.2 Reading and Resources
All the resources needed for this week and instructions on textbook reading have already been posted in module sections A1 of the first three weeks. Refer to those pages and links, regularly. In addition, the link below will be useful.
Link for Exceptions:
5A.1.3 Overloading Ambiguity
Let's start by reviewing the golden rule of function overloading:
Any combination of overloading and default parameters that results in ambiguity will result in a compiler error.
Looking at the constructors, for example:
MutualFund(int rate = 0); MutualFund(char *nm); MutualFund(char *nm, char *obj, float one, float three, float five); MutualFund(const MutualFund &mf);
we can verify that there is no possible calling signature that would result in ambiguity.
However, if we added either of the following constructors to the list, we would have problems:
MutualFund(char *nm, char *obj = "biotech"); MutualFund(char *obj = "energy");
Compare each of these to the above list and find out where the ambiguity would arise in each. This is simple exercise that should only take you a minute.
5A.1.4 No-Parameter Constructors and Arrays
Normally, when designing a class you should supply at least one default constructor, that is, a constructor that takes no parameters or one in which all of its parameters are default. Keeping in mind the inviolable ambiguity clause above, clearly we can't have both of these constructors in a single class. If you want to declare an array of objects without specifying parameters, then a no-parameter constructor must exist. If the only constructor of a class MutualFund was this:
MutualFund(int rate);
then we would not be able to do either of these things:
MutualFund portfolio[10];
or
MutualFund *portfolio = new MutualFund[10];
Moreover, even if we were willing to use initializers in the first example and do something like:
MutualFund portfolio[10] = { 5, 4, 5, 3, // etc. ...
there is no way to fix the second example; you just can't initialize a block of heap objects using parameters. Sorry.
5A.1.5 Implied Constructor Calls
A very nice feature which comes free-of-charge with all C++ compilers is this: you can often call a function that expects an object as a parameter and pass it instead, some other data type, one for which the function isn't even overloaded.
void howl( Dog d ); // expects a Dog // and no overloads
You call
howl("Fido");
and get away with it. "How can this be?" you ask. The trick is that Dog must have a constructor that takes a string or CString:
Dog::Dog(char *s);
The compiler doesn't find any howl overloads that take a string; It knows that the one and only howl() method requires a Dog object. So before giving up completely, it checks to see if it can construct a Dog from a string. If there is a constructor that does this then it creates a Dog for you and passes it in. Be careful, as usual. When the compiler turns the string "Fido" into a Dog for the function, the Dog object is a temporary object and will be destructed when the function howl() ends. Normally, that is not a problem. (Can you think of a time when it would be? Hint: think deep data without a copy constructor).
This feature is going to be important this week because we are studying operator overloading. When overloading binary operators like +, we sometimes want to use this implied constructor call mechanism. It will allow us to add not only two objects (object + object), but an object and an int (object + int), or string (object + string), or anything that the constructor can turn into an object.
This all works when and only when:
- We have a constructor that accepts a single parameter (either explicitly or as a result of default paremeters), and
- We initialize an object using the data type taken by the one-parameter constructor.
When you have these conditions you can do the following:
- Initialize an object using an assignment operator during instantiation: Dog myDog = "fido";
- Assign a value of that one-parameter constructor's argument type even after construction: myDog = "fido";
- Pass an argument of that one-parameter constructor's argument type to a function that expects an object: howl("fido");
5A.1.6 Explicit Constructors
Many programmers want strong-type enforcement by the compiler. This means, they do not want the compiler to liberally convert a string like "fido" into a Dog object, as needed. The reasoning is that they would never attempt to use that feature, and any such attempt should be regarded as a mistake to be flagged by the compiler in the form of a compiler error.
If you want strong typing, you can turn off this behind-the-scenes type conversion by declaring the one-parameter constructor explicit:
class Dog { explicit Dog(string name); // ... }
Now, none of the above shortcuts will work, because the compiler knows not to use this constructor unless the client calls it properly: Dog myDog("chloe");.