Constructors in C++

Every class defines how its objects are initialized. A class controls the initialization of its objects through one or more special member functions called constructors. The job of a constructor is to initialize the data members of a class object, and a constructor runs whenever an object of the class is created.

A constructor has the same name as the class and has no return type. A class can have multiple constructors. Like other overloaded functions, different constructors must differ in the number or types of their parameters.

A constructor cannot be declared const. While a const object is being created, values can be written into its members; the object does not take on its const property until creation is complete.

Default Constructors

When we do not provide a constructor for a class, the compiler implicitly defines one for us. The constructor created by the compiler is called the synthesized default constructor. It initializes the class’s data members according to the following rules:

  • If there is an in-class initializer, it is used to initialize the member.
  • Otherwise, the member is default-initialized.

The synthesized default constructor is suitable only for very simple classes. For ordinary classes, we must define our own constructors, for the following reasons:

  1. The compiler creates a default constructor only when it discovers that the class contains no constructors at all.
  2. For some classes, the synthesized default constructor may do the wrong thing. For example, an array or pointer that is default-initialized will have an undefined value.
  3. Sometimes the compiler cannot create a default constructor for certain classes. If the class contains a member of another class type and that member has no default constructor, then the compiler cannot perform default initialization.

When we need both ordinary constructors and a default constructor, we can use =default to define it:

class Sales_data{
	Sales_data()=default;  //default constructor
	Sales_data(const std::string &s):bookNo(s){}
}

Constructor Initializer Lists

For the other constructors of the Sales_data class:

Sales_data(const std::string &s):bookNo(s){}  //s is the initial value of bookNo
Sales_data(const std::string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
//s is the initial value of bookNo, n is the initial value of units_sold, p*n is the initial value of revenue

The new part that appears between the parameter list and the curly braces is called the constructor initializer list. It is responsible for assigning initial values to one or more data members of the newly created object. Also note that a constructor should not casually override an in-class initializer unless the new value differs from the original.

Note that members are initialized in the order in which they appear in the class definition: the first member is initialized first, then the second, and so on. The relative ordering of initial values in the constructor initializer list does not affect the actual order of initialization, so it is best to keep the order of the constructor’s initial values consistent with the order of the member declarations.

class X{
	int i;
	int j;
public:
	X(int val):j(val),i(j){}
	//this causes an error, because i is initialized before j
};

Delegating Constructors

The C++11 standard extended the functionality of constructor initializers, allowing us to define delegating constructors. A delegating constructor uses another constructor of its own class to carry out its own initialization, or in other words, delegates some or all of its responsibilities to another constructor.

class Sales_data{
public:
	Sales_data(std::string s,unsigned cnt,double price):bookNo(s),units_sold(cnt),revenue(cnt*price){}
	//the following constructors all delegate to other constructors
	Sales_data():Sales_data(" ",0,0){}
	Sales_data(std::string s):Sales_data(s,0,0){}
	Sales_data(std::istream &is):Sales_data(){read(is,*this);}
}

Implicit Class-Type Conversions

In a given class, the constructor that takes a string and the constructor that takes an istream each define a conversion rule from that type to the class:

string null_book="9999";
item.combine(null_book);
//the compiler automatically creates a Sales_data object from the given string

Note that the compiler performs only one step of type conversion. For example, a conversion from a string literal to a string object and then to a Sales_data object is not allowed.

When we do not want type conversion to happen, we can suppress it with the explicit keyword:

class Sales_data{
public:
	Sales_data()=default;
	explicit Sales_data(const std::string &s):bookNo(s){} //cannot convert from a string object to a Sales_data object
	explicit Sales_data(std::istream&);
}

When implicit conversion is not used, we can use an explicit conversion instead:

item.combine(static_cast<Sales_data>(cin));