Each class defines how objects of its type can be initialized. Classes control object initialization by defining one or more special member functions known as constructors. The job of a constructor is to initialize the data members of a class object. A constructor is run whenever an object of a class type is created.
In this section, we’ll introduce the basics of how to define a constructor. Constructors are a surprisingly complex topic. Indeed, we’ll have more to say about constructors in § 7.5 (p. 288), § 15.7 (p. 622), and § 18.1.3 (p. 777), and in Chapter 13.
Constructors have the same name as the class. Unlike other functions, constructors have no return type. Like other functions, constructors have a (possibly empty) parameter list and a (possibly empty) function body. A class can have multiple constructors. Like any other overloaded function (§ 6.4, p. 230), the constructors must differ from each other in the number or types of their parameters.
Unlike other member functions, constructors may not be declared as const
(§ 7.1.2, p. 258). When we create a const
object of a class type, the object does not assume its “const
ness” until after the constructor completes the object’s initialization. Thus, constructors can write to const
objects during their construction.
Our Sales_data
class does not define any constructors, yet the programs we’ve written that use Sales_data
objects compile and run correctly. As an example, the program on page 255 defined two objects:
Sales_data total; // variable to hold the running sum
Sales_data trans; // variable to hold data for the next transaction
The question naturally arises: How are total
and trans
initialized?
We did not supply an initializer for these objects, so we know that they are default initialized (§ 2.2.1, p. 43). Classes control default initialization by defining a special constructor, known as the default constructor. The default constructor is one that takes no arguments.
As we’ll, see the default constructor is special in various ways, one of which is that if our class does not explicitly define any constructors, the compiler will implicitly define the default constructor for us
The compiler-generated constructor is known as the synthesized default constructor. For most classes, this synthesized constructor initializes each data member of the class as follows:
• If there is an in-class initializer (§ 2.6.1, p. 73), use it to initialize the member.
• Otherwise, default-initialize (§ 2.2.1, p. 43) the member.
Because Sales_data
provides initializers for units_sold
and revenue
, the synthesized default constructor uses those values to initialize those members. It default initializes bookNo
to the empty string.
Only fairly simple classes—such as the current definition of Sales_data
—can rely on the synthesized default constructor. The most common reason that a class must define its own default constructor is that the compiler generates the default for us only if we do not define any other constructors for the class. If we define any constructors, the class will not have a default constructor unless we define that constructor ourselves. The basis for this rule is that if a class requires control to initialize an object in one case, then the class is likely to require control in all cases.
The compiler generates a default constructor automatically only if a class declares no constructors.
A second reason to define the default constructor is that for some classes, the synthesized default constructor does the wrong thing. Remember that objects of built-in or compound type (such as arrays and pointers) that are defined inside a block have undefined value when they are default initialized (§ 2.2.1, p. 43). The same rule applies to members of built-in type that are default initialized. Therefore, classes that have members of built-in or compound type should ordinarily either initialize those members inside the class or define their own version of the default constructor. Otherwise, users could create objects with members that have undefined value.
Classes that have members of built-in or compound type usually should rely on the synthesized default constructor only if all such members have in-class initializers.
A third reason that some classes must define their own default constructor is that sometimes the compiler is unable to synthesize one. For example, if a class has a member that has a class type, and that class doesn’t have a default constructor, then the compiler can’t initialize that member. For such classes, we must define our own version of the default constructor. Otherwise, the class will not have a usable default constructor. We’ll see in § 13.1.6 (p. 508) additional circumstances that prevent the compiler from generating an appropriate default constructor.
Sales_data
ConstructorsFor our Sales_data
class we’ll define four constructors with the following parameters:
• An istream&
from which to read a transaction.
• A const string&
representing an ISBN, an unsigned
representing the count of how many books were sold, and a double
representing the price at which the books sold.
• A const string&
representing an ISBN. This constructor will use default values for the other members.
• An empty parameter list (i.e., the default constructor) which as we’ve just seen we must define because we have defined other constructors.
Adding these members to our class, we now have
struct Sales_data {
// constructors added
Sales_data() = default;
Sales_data(const std::string &s): bookNo(s) { }
Sales_data(const std::string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) { }
Sales_data(std::istream &);
// other members as before
std::string isbn() const { return bookNo; }
Sales_data& combine(const Sales_data&);
double avg_price() const;
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
= default
MeansWe’ll start by explaining the default constructor:
Sales_data() = default;
First, note that this constructor defines the default constructor because it takes no arguments. We are defining this constructor only because we want to provide other constructors as well as the default constructor. We want this constructor to do exactly the same work as the synthesized version we had been using.
Under the new standard, if we want the default behavior, we can ask the compiler to generate the constructor for us by writing = default
after the parameter list. The = default
can appear with the declaration inside the class body or on the definition outside the class body. Like any other function, if the = default
appears inside the class body, the default constructor will be inlined; if it appears on the definition outside the class, the member will not be inlined by default.
The default constructor works for Sales_data
only because we provide initializers for the data members with built-in type. If your compiler does not support in-class initializers, your default constructor should use the constructor initializer list (described immediately following) to initialize every member of the class.
Next we’ll look at the other two constructors that were defined inside the class:
Sales_data(const std::string &s): bookNo(s) { }
Sales_data(const std::string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) { }
The new parts in these definitions are the colon and the code between it and the curly braces that define the (empty) function bodies. This new part is a constructor initializer list, which specifies initial values for one or more data members of the object being created. The constructor initializer is a list of member names, each of which is followed by that member’s initial value in parentheses (or inside curly braces). Multiple member initializations are separated by commas.
The constructor that has three parameters uses its first two parameters to initialize the bookNo
and units_sold
members. The initializer for revenue
is calculated by multiplying the number of books sold by the price per book.
The constructor that has a single string
parameter uses that string
to initialize bookNo
but does not explicitly initialize the units_sold
and revenue
members. When a member is omitted from the constructor initializer list, it is implicitly initialized using the same process as is used by the synthesized default constructor. In this case, those members are initialized by the in-class initializers. Thus, the constructor that takes a string
is equivalent to
// has the same behavior as the original constructor defined above
Sales_data(const std::string &s):
bookNo(s), units_sold(0), revenue(0){ }
It is usually best for a constructor to use an in-class initializer if one exists and gives the member the correct value. On the other hand, if your compiler does not yet support in-class initializers, then every constructor should explicitly initialize every member of built-in type.
Constructors should not override in-class initializers except to use a different initial value. If you can’t use in-class initializers, each constructor should explicitly initialize every member of built-in type.
It is worth noting that both constructors have empty function bodies. The only work these constructors need to do is give the data members their values. If there is no further work, then the function body is empty.
Unlike our other constructors, the constructor that takes an istream
does have work to do. Inside its function body, this constructor calls read
to give the data members new values:
Sales_data::Sales_data(std::istream &is)
{
read(is, *this); // read will read a transaction from is into this object
}
Constructors have no return type, so this definition starts with the name of the function we are defining. As with any other member function, when we define a constructor outside of the class body, we must specify the class of which the constructor is a member. Thus, Sales_data::Sales_data
says that we’re defining the Sales_data
member named Sales_data
. This member is a constructor because it has the same name as its class.
In this constructor there is no constructor initializer list, although technically speaking, it would be more correct to say that the constructor initializer list is empty. Even though the constructor initializer list is empty, the members of this object are still initialized before the constructor body is executed.
Members that do not appear in the constructor initializer list are initialized by the corresponding in-class initializer (if there is one) or are default initialized. For Sales_data
that means that when the function body starts executing, bookNo
will be the empty string
, and units_sold
and revenue
will both be 0
.
To understand the call to read
, remember that read
’s second parameter is a reference to a Sales_data
object. In § 7.1.2 (p. 259), we noted that we use this
to access the object as a whole, rather than a member of the object. In this case, we use *this
to pass “this” object as an argument to the read
function.
Exercise 7.11: Add constructors to your Sales_data
class and write a program to use each of the constructors.
Exercise 7.12: Move the definition of the Sales_data
constructor that takes an istream
into the body of the Sales_data
class.
Exercise 7.13: Rewrite the program from page 255 to use the istream
constructor.
Exercise 7.14: Write a version of the default constructor that explicitly initializes the members to the values we have provided as in-class initializers.
Exercise 7.15: Add appropriate constructors to your Person
class.
3.147.47.166