Constructors

Constructor is a special kind of class function that is used to define the initial state of an object. That means that in order to create an object of some class, we need to call its constructor.
 
Constructor:
  • has the same name as its class
  • does not have a return value(not even void)
  • can have an arbitrary number of parameters of any type(except the type of that class(pointers and references are allowed))
  • can have default arguments
 
There are several types of constructors: default, parametrized, copy, move, delegating
(they will be explained in detail below)
class MyName{
int a;
char b;
public:
  MyName();//constructor without parameters
  MyName(int a);//constructor with 1 parameter
  MyName(char b, int a=1);//constructor with a default argument
  MyName(Myname mn);//ERROR!!! Constructor cannot have argument of its own class
  MyName(MyName* mn);//this is okay
  MyName(MyName& mn);//this is okay
 
};

Calling constructor

The constructor is called whenever a class object is created. Constructors can be called in couple of different forms;
  • Type identifier; // if object has Type( ) constructor(constructor without parameters)
  • Type identifier(argument1,argument2,…); //if object has constructor that is compatible with these values
  • Type identifier{arguments…};
  • Type identifier = {argument1,argument2, …}; //same as the previous one
  • Type identifier = value; // if there is a compatible constructor with only one parameter
  • Type* identifier = new Type(parameters);
 
Constructor cannot be called with Type identifier() , it would be seen as a function declaration.
The constructor that is called is the one whose signature fits best. In the following function declaration the bold text is called a signature: <ReturnType> function_name(parameters…)modifiers;
int main(){
  MyName a;//calling MyName()
  MyName b();//function declaration
  MyName c('a',1);//calling MyName(char int)
  MyName c2('b');//calling MyName(char int) because int has a default value
  MyName d=3;//calling MyName(int)
  MyName e(3,4,2);//Error, no such constructor exists
  MyName f(&a);//calling MyName(MyName*)
  MyName g(b);//calling MyName(MyName&)
}

Constructors can also be called explicitly. A call like that will create a temporary object.

MyName mn;
mn=MyName('a',1); 

Constructor definition

General definition of a constructor is:
ClassName (parameters) : initializers { body contents…. }
In case the body definition is outside of the class definition we need to use the operator : : ( <ClassName>::<ClassName>(…){….} ). The operator is used for methods and static attributes also.
 
Additionally, the constructor can also have something called the member initializer list, which serves the purpose of initializing class fields. It is placed between the constructor’s signature and its body. The existence of a member initializer list is specified with a colon ( : ). Initializers are separated by a comma. If there are no initializers then : is not needed.
The general form of an initializer is: fieldName(expression1,expression2,…)
fieldName is an identifier of a field that we want to initialize and expressions are arguments of the constructor for that identifier. If the fieldName is a primitive type (int, bool, char…) then it can be initialized with only one expression (that fits the type).
Also, initialization of fields is not the same as assignment! The assignment happens in the body of the constructor.
class C {
  int a;
  char b;  

public:
  C():a(1),b('a'){}//initializing a and b
//or
  C(){a=1;b='a';}//assigning value to a and b

  C(int i):a(i+2),b('e'){}
//or
  C(int i):a(i+2){b='e';}//...
  
};

Default constructor

Default constructor is a constructor that can be called without real arguments, meaning it either has no arguments or every argument has a default value.
When there is no user-defined constructors, the default constructor is implicitly defined without any parameters and with an empty body. If there is even one user-defined constructor in that class, the “hidden” default constructor will cease to exist. When an array of objects of some class is made, every one of them calls the default constructor. Also it should be noted that if some data members of the class are pointers, an explicit default constructor with a non-empty body is a must (this will be further explained in constructor execution flow).

Delegating constructor

In the initializer list of a constructor we can call another (target) constructor of the same class. The one that calls that target constructor is called the delegating constructor. When we do this, the target constructor executes first and then the delegating one. Also when we do this, there cannot be any other member of that initializer list besides that target constructor. We also must be wary that this can lead to recursion if not done properly (the compiler will not detect it for us so it will go into an infinite loop).

class R {
public:
  R(int i){}
  R():R(1){} // R() is delegating, R(int) is target
  R(char c): R(0.1){} //ERROR-recursion R(char) will call R(double) and the other way around
  R(double d): R(’c’){}
};

Copy constructor

Copy constructor is like the default one, a unique type of constructor. It has one parameter and that is a reference to its own class (it can have multiple parameters if they have defaulted values) . When called, the appropriate argument can be an object or a reference to an object.
The use of this constructor is to initialize an object by copying the contents of another object (that is being passed as an argument). If the copy constructor is not defined within the class, it is generated automatically and it will copy values of all data members from a passed object (original) to an object that is being created. That type of copying is called Shallow Copy. An automatically generated constructor is public. If some data members are objects of another classes, their copy constructors will be called for copying also.
class CC{
public:
  CC(int i) { 
   this.field = i;
  }
  CC(const CC& target){
    target.field = this.field;
  } // copy constructor
private:
  int field;
};

CC f(CC c1) {
  CC c2=c1; // call of the copy constructor for c2
  return c2; // call of the copy constructor for returning a value
} 

void h() {
  CC cc1=1, cc2=4;
  cc1=f(cc2);/*Call of the copy constructor three times! Once from cc2 ➝ function parameter
  c1, once from function parameter c1 ➝ function variable c2, once from return function
  value ➝ cc1*/
}

Now if some data members are pointers, the generated constructor will only copy the value those pointers have, it will not make a copy of actual objects the pointers are pointing to (so basically pointers of both objects will then point to the same thing). In most cases, that is not the effect we want That’s why we need to make our own copy constructors when pointers are involved and do a Deep Copy, where we don’t want both pointers to point to the same location but rather allocate new memory resources with the same value.

p1

If we want our class to forbid the use of the copy constructor (or any other constructor), then we can just simply define the copy constructor in private section or write the following:

class C {

...

  C(const C&) = delete;

...

};

This will delete the specified constructor.

Conversion constructor

Conversion constructor is a constructor that can be called with only one argument (of course it can have more if other parameters have default values). The purpose of this kind of constructor is self-explanatory by its name: It converts one data type into another. The catch here is that the resulting data type is a class. Additionally, the data type which is being converted can also be a class. If we have for example class A, and that class has a constructor A: :A( B& ), where B is either a class or a primitive type, the mentioned constructor can be used to convert an object of type B to an object of type A. If we have an instance of B b, then the value of expression A(b) is a temporary (unnamed) object of type A.
Note that conversion constructors can convert primitive types to class types, but NOT the other way around. That’s because primitive types are not classes that can have user-defined conversion constructors. The conversion forms can be A(b), (A)b, static_cast<A>(b) .

Constructor execution flow

Before executing the body of the constructor
  • Attributes of primitive types are initialized
  • constructors of data members (which are of user-defined types) are called
  • If an attribute (data member) has its initializer in the class body and in the constructor definition, the initializer from the constructor definition is applied !!
 
Initialization of attributes is done in the order of listing in the class, no matter if they are primitive or class type and regardless of the order in the initializer list.
 
Initialization is needed:
  • when the default constructor does not exist
  • when an attribute is a constant
  • when an attribute is a reference
Initialization of class type attributes :
Their constructor is called, if it’s not specified explicitly then a default constructor is called (that class then HAS TO HAVE IT). It is highly advised to initialize every class type attribute in the initializer list rather than assigning a value in body of a constructor.
 
If initialization is not used then primitive type attributes will have garbage values.

Initializing an array of class objects

Just like with arrays of primitive types, we can use initializers in the form of a list of expressions inside curly brackets. Those expressions can be even constructor calls.

MyName mn[] = { 12, MyName( 'a', 2), MyName( ) };

If initializers are excluded, then for every object of the array, the default constructor is called (the compiler signals an error if it doesn’t exist). Objects are initialized in ascending order of an index (or to be precise in ascending order of addresses). If the operator new is used, then initializers cannot be used and only the default constructor is called for each element of the list (again, the compiler will signal an error if it doesn’t exist).

Leave a Reply