Using const in classes
Remember that const can be used in a variety of places. And
everywhere it is applied in code, it:
- Expresses an intent on the part of the programmer, which the compiler
enforces (i.e. something is not allowed to change, within some scope)
- Expresses the intent more clearly to a user (in this case,
another portion of code -- i.e. maybe another programmer)
- Affects how certain items can be used.
- Sometimes this is the difference between using L-values vs. R-values
in calls to functions
- Sometimes this affects what other items can be used (whether they are
also const or not
)
Revisiting const reference parameters
- Pass-by-value vs. Pass-by-reference works the same on objects as it
does with built-in types
- If an object is passed by value, a copy is made of the object. Any
R-value can be sent on the call
- If an object is passed by reference (without const), no copy
is made, and only an L-value can be sent on the call
- Objects can be passed by const reference, as well. This way,
no copy is made (less overhead), but the object cannot be changed through
the reference. Since objects are sometimes large, this is often
desirable
- This example used pass-by-value parameters:
friend Fraction Add(Fraction f1, Fraction f2);
We definitely don't want to change the original fractions that were sent
in. But to save overhead, we could use const reference parameters:
friend Fraction Add(const Fraction& f1, const Fraction& f2);
Since the parameters are const, R-values can be sent in
(just like with pass-by-value).
const Member Functions
Declaring const objects
- Declaring primitive type variables as const is easy. Remember
that they must be initialized on the same line:
const int SIZE = 10;
const double PI = 3.1415;
- Objects can also be declared as const. The constructor will
always run to intialize the object, but after that, the object's state
(i.e. internal data) cannot be changed
const Fraction ZERO; // this fraction is fixed at 0/1
const Fraction FIXED(3,4); // this fraction is fixed at 3/4
- To ensure that a const object cannot be changed, the
compiler enforced the following rule:
- A const object may only call const member
functions
- So, using the Fraction class example above with const member
functions, the following calls are legal:
FIXED.Show(); // calling const functions
cout << FIXED.Evaluate();
int n = ZERO.GetNumerator();
int d = ZERO.GetDenominator();
The following calls would be illegal and would cause compiler
errors:
FIXED.SetValue(5,7);
ZERO.Input();
Note that in the original version of Fraction (with no const
member functions), ALL of these calls would result in compiler
errors, even if the function itself didn't change anything (like
Show).
- Examples:
const Member Data
- Member data of a class can also be declared const. This is a
little tricky, because of certain syntax rules.
- Remember, when a variable is declared with const in a normal
block of code, it must be initialized on the same line:
const int SIZE = 10;
- However, it is NOT legal to intialize the member data variables
on their declaration lines in a class definition block:
class Thing
{
public:
Thing(); // constructor -- intialize member data in here
// blah blah blah
private:
int x; // just declare here
int y = 0; // this would be ILLEGAL! cannot initialize here
const int Z = 10; // would also be ILLEGAL
};
- But a const declaration cannot be split up into a regular
code block. This attempt at a constructor definition would also
not work, if Z were const:
Thing::Thing()
{
x = 0;
y = 0;
Z = 10;
}
- Solution: When we have lines like this, which involve
declaring and initializing in one step and cannot be split up in normal
code, we handle it with a special section of a function called the
initialization list. Format:
returnType functionName(parameterList) : initialiation_list
{
// function body
}
- Simple class example that
illustrates the initialization of a const member data variable
Destructors
In addition to the special Constructor function, classes also have a special
function called a destructor.
Declaration:
The destructor looks like the default constructor (constructor with
no parameters), but with a ~ in front. Destructors cannot have parameters,
so there can only be one destructor for a class. Example: The
destructor for the Fraction class would be:
~Fraction();
Like the constructor, this function is called automatically (not explicitly)
-
A constructor is called automatically when an object is created.
-
A destructor is called automatically right before an object is deallocated
by the system (usually when it goes out of scope).
The destructor's typical job is to do any clean-up tasks (usually
involving memory allocation) that are needed, before an object is deallocated.
However, like a constructor, a destructor is a function and can do other
things, if desired.
Compile and run this example to see when
constructors and destructors are invoked.