Professional C__ - Marc Gregoire [77]
Constructors on the Stack
When you allocate a SpreadsheetCell object on the stack, you use the constructor like this:
SpreadsheetCell myCell(5), anotherCell(4);
cout << "cell 1: " << myCell.getValue() << endl;
cout << "cell 2: " << anotherCell.getValue() << endl;
Code snippet from SpreadsheetCellCtors\SpreadsheetCellTest.cpp
Note that you do NOT call the SpreadsheetCell constructor explicitly. For example, do not use something like the following:
SpreadsheetCell myCell.SpreadsheetCell(5); // WILL NOT COMPILE!
Similarly, you cannot call the constructor later. The following is also incorrect:
SpreadsheetCell myCell;
myCell.SpreadsheetCell(5); // WILL NOT COMPILE!
Again, the only correct way to use the constructor on the stack is like this:
SpreadsheetCell myCell(5);
Constructors on the Heap
When you dynamically allocate a SpreadsheetCell object, you use the constructor like this:
SpreadsheetCell *myCellp = new SpreadsheetCell(5);
SpreadsheetCell *anotherCellp = nullptr;
anotherCellp = new SpreadsheetCell(4);
// ... do something with the cells
delete myCellp;
myCellp = nullptr;
delete anotherCellp;
anotherCellp = nullptr;
Code snippet from SpreadsheetCellCtors\SpreadsheetCellTest.cpp
Note that you can declare a pointer to a SpreadsheetCell object without calling the constructor immediately, which is different from objects on the stack, where the constructor is called at the point of declaration.
Whenever you declare a pointer on the stack or in a class, it should be initialized to nullptr like in the previous declaration for anotherCellp. If you don’t assign it to nullptr, the pointer is undefined. Accidentally using an undefined pointer will cause unexpected and difficult-to-diagnose memory corruption. If you initialize it to nullptr, using that pointer will cause a memory access error in most operating environments, instead of producing unexpected results.
As usual, remember to call delete on the objects that you dynamically allocate with new or use smart pointers!
Providing Multiple Constructors
You can provide more than one constructor in a class. All constructors have the same name (the name of the class), but different constructors must take a different number of arguments or different argument types. In C++, if you have more than one function with the same name, the compiler will select the one whose parameter types match the types at the call site. This is called overloading and is discussed in detail in Chapter 7.
In the SpreadsheetCell class, it is helpful to have two constructors: one to take an initial double value and one to take an initial string value. Here is the class definition with the second constructor:
class SpreadsheetCell
{
public:
SpreadsheetCell(double initialValue);
SpreadsheetCell(string initialValue);
// Remainder of the class definition omitted for brevity
};
Code snippet from SpreadsheetCellCtors\SpreadsheetCell.h
Here is the implementation of the second constructor:
SpreadsheetCell::SpreadsheetCell(string initialValue)
{
setString(initialValue);
}
Code snippet from SpreadsheetCellCtors\SpreadsheetCell.cpp
And here is some code that uses the two different constructors:
SpreadsheetCell aThirdCell("test"); // Uses string-arg ctor
SpreadsheetCell aFourthCell(4.4); // Uses double-arg ctor
SpreadsheetCell* aThirdCellp = new SpreadsheetCell("4.4"); // string-arg ctor
cout << "aThirdCell: " << aThirdCell.getValue() << endl;
cout << "aFourthCell: " << aFourthCell.getValue() << endl;
cout << "aThirdCellp: " << aThirdCellp->getValue() << endl;
delete aThirdCellp;
aThirdCellp = nullptr;
Code snippet from SpreadsheetCellCtors\SpreadsheetCellTest.cpp
When you have multiple constructors, it is tempting to attempt to implement one constructor in terms of another. For example, you might want to call the double constructor from the string constructor as follows:
SpreadsheetCell::SpreadsheetCell(string initialValue)
{
SpreadsheetCell(stringToDouble(initialValue));