User-Defined Types
8.8 Constructors with parameters
int␣main()␣{
␣␣␣Point␣p;
␣␣␣cout␣<<␣"What␣is␣the␣value␣of␣x?\n";
␣␣␣cout␣<<␣p.x;
␣␣␣return␣0;
}
In the definition of the Point constructor, you can see a list of statements:
␣␣␣␣x(0.0),
␣␣␣␣y(0.0)
This is called an initialisation list. This calls constructors for x and y in order to initialise the data. This is slightly different from our earlier code which used an = statement to initialise the variables.
The general syntax for a constructor with an initialisation list is:
CLASS_NAME::CLASS_NAME( PARAMETER_LIST) : INITIALISATION_LIST {
... NORMAL CODE ...
}
Notice the colon before the initialisation list.
Experienced C++ programmers prefer an initialisation list because it is marginally faster. This is because calling the default constructor and then performing assignment will be slower than using the right value the first time.
In addition, once you are familiar with the syntax it is more readable because it is immediately obvious that there is nothing clever going on. All that is happening is that data is being initialised. This means that you can guess that if there is some code between the curly brackets it will be relatively interesting.
8.8 Constructors with parameters
Writing constructors that take parameters is straightforward. Here is how one could add a constructor that takes a strike and a maturity as parameters to the CallOption class.
class␣CallOption␣{
public:
␣␣␣␣double␣strike;
␣␣␣␣double␣maturity;
␣␣␣␣CallOption();␣//␣default␣constructor
␣␣␣␣CallOption(double␣strike,␣double␣maturity);//alternative };
//␣default␣constructor␣implementation CallOption::CallOption()␣:
␣␣␣␣strike(0.0),
␣␣␣␣maturity(0.0)␣{
}
//␣alternative␣constructor␣implementation CallOption::CallOption(
␣␣␣␣␣␣␣␣double␣s,
␣␣␣␣␣␣␣␣double␣m␣)␣:
␣␣␣␣strike(s),
␣␣␣␣maturity(m)␣{
}
To create an option with strike 100 and maturity 2.0 one would then write CallOption␣myOption(␣100,␣2.0␣);
This may seem like a very convenient system, but it is probably not a very good design. The reason is that by introducing classes we got rid of the problem of having to remember in what order to put parameters when calling functions.
By having a constructor with multiple parameters, we’ve reintroduced this problem!
This doesn’t mean that constructors that take parameters are always bad, just that you shouldn’t over-use them. Certainly the ability to construct a vector of given size and default value is a good use of constructors.
There is one strange trick with parameterised constructors that you should try to remember. Consider the class string. It has a constructor that takes raw text data:
string␣s("Some␣raw␣text");
C++ uses this constructor to automatically convert raw text data to strings without your having to think about it. For example, the code:
plot(␣"myPlot.txt",␣xVec,␣yVec␣);
will work despite the fact that the first parameter of a plot is declared to be a const␣string& and not a char*.
In general, if your class has a constructor that takes a single parameter construction, C++ will perform similar automatic conversions. While this is great for the string data type, usually it results in code that is very unnatural.
For example, suppose you had added a constructor to BlackScholesModel where you provide just the stock price:
class␣BlackScholesModel␣{
public:
␣␣␣␣double␣stockPrice;
␣␣␣␣double␣data;
␣␣␣␣double␣volatility;
␣␣␣␣double␣riskFreeRate;
␣␣␣␣BlackScholesModel();
␣␣␣␣BlackScholesModel(␣double␣stockPrice␣);␣//␣key␣line };
Doing this means that C++ will now automatically convert doubles into in-stances of BlackScholesModel. This is a very odd thing to do, and is unde-sirable. To prevent this unwanted behaviour, you should mark constructors that take one parameter as explicit.
class␣BlackScholesModel␣{
public:
␣␣␣␣double␣stockPrice;
␣␣␣␣double␣data;
␣␣␣␣double␣volatility;
␣␣␣␣double␣riskFreeRate;
␣␣␣␣BlackScholesModel();
␣␣␣␣explicit␣BlackScholesModel(␣double␣stockPrice␣);␣//␣key␣line };
The code above prevents automatic conversion. In the very rare event that automatic conversion is useful (as for strings) you can drop the explicit.
If you forget the explicit it isn’t the end of the world. The confusions that may arise from forgetting to use it are quite rare in practice.
Exercises
8.8.1. Write a default constructor for every class that you have created so far. Use both forms of the constructor syntax to familiarise yourself with the options.
8.8.2. Write a class Polynomial that represents the polynomial a0+ a1x + a2x2+ . . . + xn.
Here the coefficients aiare doubles which associated with the polynomial itself, but x is an unknown.
The class Polynomial should have the following features.
• It should store the coefficients ai in a vector.
• It should have a function evaluate which takes as a parameter x and evaluates the polynomial at x.
• It should have a constructor which takes a vector of coefficients.
• It should have a function add which can be used to add two polynomials.
• It should have a default constructor which generates the constant zero polynomial.
• It should have a constructor which takes a single double c as a param-eter and generates the constant polynomial with a0 = c and all other coefficients 0.
8.9 Summary
• We’ve learned how to write classes that group data together.
• We’ve learned how to add member functions to our classes.
• We’ve seen that using classes makes life easier for users of our library.
• We’ve learned how to use the keywords public and private to hide data from users of our library.
• We’ve learned the buzzword “encapsulation”.
• We’ve learned how to use the const keyword to indicate whether or not an object is changed by calling one of its member functions.
• We have learned that classes should normally have a constructor to initialise the data.