Inspector functions (access functions):
Return information about an object’s state, or display some or all of an object’s attributes.
getName() and displayValues()
A subcategory are predicate (logic) functions, such as isDigit(), fail() - to test various conditions.
Mutator functions (implementors):
Functions that change an object’s attributes.
setData() and processValue().
Auxiliary functions (facilitators):
Functions to perform actions or services.
sortAscending() or findLowestValue()
Object Management functions (manager functions):
Constructors - Create objects.
Destructors - Destroy objects.
A Constructor function is called automatically each time an object is created.
Constructors are used to initialize the object in a specified way.
They are functions with the same name as the class, and no return type.
There are two (basic) types:
Default constructor: No arguments.
Non-default constructors: At least one argument.
class energyBill {
private:
double totalAmount;
double totalGST;
int energyUsed;
string dueDate;
int refNumber;
static double rate;
static const int billerCode;
public:
energyBill();
energyBill(double, double, int, string, int);
energyBill(const energyBill &);
static void changeRate(double);
void showRate() const;
void display() const;
};
energyBill :: energyBill() {
//data members are not initialized
}
energyBill :: energyBill() {
/* Initialize data members */
totalAmount = 0.0;
totalGST = 0.0;
energyUsed = 0;
dueDate = "" ;
refNumber = 0;
}
The compiler generated, or synthesized, default constructor will default initialize the data members.
Usually it’s necessary to define your own default constructor since the compiler generates a default constructor if you define no constructors at all, even if the ones you define are all non-default.
We can tell a C++11 compliant compiler to generate the synthesized one for us as follows ...
energyBill() = default;
It makes sense to use this if we have defined a non-default constructor.
This can be in the class definition, or not, the former being inline, the latter not by default.
Destructors are called for an object whenever the object goes out of scope. They have the name as the class but with a leading tilde (~).
Like ~X() for ADT X.
One destructor per class.
Default set up by the compiler if none is specified.
No parameters, no return type. Think of using the constructor to set up objects and the destructor to remove them nicely.
Stroustrup described this as: “A constructor establishes the environment for the members to run in; the destructor reverses its actions.”
Destructors should allow memory to be released, avoiding memory leaks.
Primarily using the delete operator on any new declared data members.
They could be used to write some information about the object to a file or to standard out.
Constructors and destructors are tied up with managing resources appropriately.
This is an example of RAII: Resource Acquisition Is Initialisation.
https://en.cppreference.com/w/cpp/language/lifetime
Less commonly, but more clearly, called Scope-Bound Resource Management (SBRM). More on this later.
#include<iostream>
using namespace std;
class House {
private:
int* area;
public:
House();
~House();
};
int main()
{
House aHouse[3];
cout <<"Ending"<<endl;
}
House::House()
{
area = new int(300);
cout<<"House up!"<<endl;
cout<<this<<endl;
}
House::~House()
{
cout<<"House down!"<<endl;
cout<<this<<endl;
// delete area;
}
House up!
ffbff720
House up!
ffbff724
House up!
ffbff728
Ending
House down!
ffbff728
House down!
ffbff724
House down!
ffbff720
The last created is the first destroyed!
What if we commented out the destructor?
...
Later -> Rule of 5/3/0/6.
They are usually public but we may have a reason for making a constructor private.
We may create an object with certain values only under conditions controlled from within an object.
A private constructor can also be used in the context of inheritance.
We will see later that a non-public constructor is used to implement a design pattern called a singleton.
Well...
Sure, with care.
The destructor is usually public but we can make them private.
It can give us finality, something we will discuss in the context of inheritance.
It can be used to provide safe reference tracking.
An object may be in use by more than one reference simultaneously.
If the destructor is public, a released reference can call it and destroy the object, despite there still being references to the object.
A reference counting object tracks the number of references to itself.
It can only destroy itself when there are no references to it.
We would have a private destructor and other functions which are careful enough to invoke the destructor only if the number of references becomes 0.
#include <iostream>
using namespace std;
class Example {
private:
~Example(){}
};
int main()
{
Example ex1;
}
The compiler detects that the object ex1 cannot be destructed.
The private is the problem...
#include <iostream>
using namespace std;
class Example {
private:
~Example(){}
};
int main()
{
Example *ex2;
}
ex2 is not an object, it’s a pointer.
An object has not been constructed yet and therefore there is no need to destruct it.
#include <iostream>
using namespace std;
class Example {
private:
~Example(){}
};
int main()
{
Example *ex2 = new Example;
delete ex2;
}
delete cannot get access to the private destructor. This will
compile if you comment out the delete, but there will be a memory
leak because we aren’t tidying up correctly.