12.4 Constructors
Constructors are special member functions that are executed whenever we create new objects of a class type.
Constructors have the same name as the name of the class and may not specify a return type. Like any other function, they may define zero or more parameters.
Constructors May Be Overloaded
Constructors can be overloaded as long as the parameter list of each constructor is unique. And the argument will determine which constructor to use.
Constructors Are Executed Automatically
The compiler runs a constructor whenever an object of the type is created
Constructors for const Objects
A constructor may not be declared as const:
class Sales_item {
public:
Sales_item() const; //error
};
When we crate a const object of a class type, an ordinary constructor can still be used to initialize it.
12.4.1 The Constructor Initializer
The constructor initializer starts with a colon, which is followed by a comma-separated list of data members each of which is followed by an initializer inside parentheses.
Constructors can be defined inside or outside of the class. The constructor initializer is specified only on the constructor definition, not its declaration.
It is usually legal to omit the initializer list and assign values to the data members inside the constructor body:
// no constructor initializer
Sales_item::Sales_item(const string &book)
{
isbn = book;
units_sold = 0;
revenue = 0.0;
}
Regardless of the lack of an explicit initializer, the isbn member is initialized before the constructor is executed.
Conceptually, we can think of a constructor as executing in two phases:
1. initialization phase.
If we don't provide initializer list, then class variables will use default construtor. And built-in variables will be initialized to 0 if the object is in global scope; or uninitialized if they are in local scope
2. a general computation phase, which consists all the statements within the function body.
Constructor Initializers Are Sometimes Required
Members of a class type that do not have a default constructor and members that areconst or reference types must be initialized in the constructor initializer regardless of type. For these members, assigning to them in the constructor body does not work.P 455
This is because we can initialize but no assign to const or reference type.
We should always use constructor initializers instead of constructor body.
Order of Member Initialization
Each member may be named only once in the constructor initializer.
The constructor initializer list specifies only the values used to initialize the members, not the order in which those initializations are performed.
The order in which members are initialized is the order in which the members are defined.
class X {
int i;
int j;
public:
//run-time error: i is initialized before j
X(int val) : j(val), i(j) {} //we should first initialize i, then j
};
It is good idea to write constructor initializers in the same order as the members are declared. And avoid using members to initialize other members when possible.
Initializers May Be Any Expression
Initializers for Data Members of Class Type
When we initialize a member of class type, we can use any of that type's constructors. (The argument will decide which constructor to use)
// use a different string constructor for isbn
Sales_item(): isbn(10, '9'), units_sold(0), revenue(0.0) {}
12.4.2 Default Argument and Constructors
We prefer to use a default argument because it reduces code duplication:
// Previously we have two constructors
Sales_item(const std::string &book):
isbn(book), units_sold(0), revenue(0.0) {}
Sales_item(): units_sold(0), revenue(0.0) {}
//We can combine them by default argument
Sales_item(const std::string &book = ""):
isbn(book), units_sold(0), revenue(0.0) {}
12.4.3 The Default Constructor
The default constructor is used whenever we define an object but do not supply an initializer.
A constructor that supplies default arguments for all its parameters also defines the default constructor.
The Synthesized Default Constructor
The compiler generates a default constructor automatically only if a class defines no constructors
The synthesized default constructor initializes members using the same rules as those that apply for how variables are initialized.
If a class contains data members of built-in or compound type(pointer or reference), then the class should not rely on the synthesized default constructor.
Classes Should Usually Define a Default ConstructorP 460
In practice, it is almost always right to provide a default constructor if other constructors are being defined.
Using the Default Constructor
When use default constructor, we only need to define an object and leave off the trailing, empty parentheses:
//correct way to use default constructor
Sales_item myobj;
//wrong way, you defined a function which will return Sales_item object
Sales_item myobj();
Another way to use default constructor:
Sales_item myobj = Sales_item();
Here we value initialize a Sales_item object and use it to initialize myobj.
12.4.4 Implicit Class-Type Conversions
As the language defines several automatic conversions among the built-in types, we can also define how to implicitly convert an object from another type to our class type or to convert from our class type to another type.
To define an implicit conversion to a class type, we need to define an appropriate constructor.
A constructor that can be called with a single argument defines an implicit conversion from the parameter type to the class type.
E.G.
Sales_item(const std::string &book = ""): //constructor with one argument
isbn(book), units_sold(0), revenue(0.0) {}
//we can convert string to Sales_item object:
string null_book = "9-999-99999-9";
item.same_isbn(null_book);
same_isbn is a member function that should take Sales_item object as argument. But the compiler uses the constructor to generate a new Sales_item object from null_book. The newly generated Sales_item is passed to same_isbn.
The newly generated object is a temporary object, we have no access to it once same_isbn finishes.
Supressing Implicit Conversions Defined by Constructors
We can prevent the implicit conversion by declaring the constructor explicit:
explicit Sales_item(const std::string &book = ""):
isbn(book), units_sold(0), revenue(0.0) {}
The explicit keyword is used only on the constructor declaration inside the class. It is not repeated on a definition made outside the class body. P 462
Now the constructor can not be used to implicitly create a Sales_item object:
item.same_isbn(null_book); //error: string constructor is explicit
When a constructor is declared explicit, the compiler will not use it as a conversion operator.
Explicitly Using Constructors for Conversions
An explicit constructor can be used to generate a conversion as long as we do so explicitly:
item.same_isbn(Sales_item(null_book));
Even though the constructor is explicit, this usage is allowed. Making a constructor explicit turns off only the use of the constructor implicitly.Any constructor can be used to explicitly create a temporary object.
Ordinarily, single-parameter constructors should be explicit.
12.4.5 Explicit Initialization of Class Members
Members of classes that define no constructors and all of whose data members are public may be initialized explicitly:
struct Data {
int a;
char *ptr;
}
Data val1 = {0, 0};
Data val2 = {1024, "Anna"};
val1.ival = 1024;
val1.ptr = "Anna";
The initializers are used in the declaration order of the data members.
12.5 Friends
In some cases, specific nonmember functions need to access the private members of a class. E.G. input or output operators.
The friend mechanism allows a class to grant access to its nonpublic members to specified functions or classes.
A friend declaration begins with the keyword friend.
Friend declaration may appear only within a class definition. It may appear anywhere in the class: they are not affected by the access control.
Ordinarily,we group friend declaration together either at the beginning or end of the class definition.
Friendship example: P 465
//friend declaration end with ;
class Screen {
friend class Window_Mgr;
};
A friend may be an ordinary, nonmember function, a member function of another class, or an entire class. If we make a class a friend, all the member functions of the friend class are given access to the nonpublic members of the class granting friendship.
Making Another Class' Member Function a Friend
When we declare a member function to be a friend, the name of the function must be qualified by the name of the class of which it is a member.
class Screen {
// Window_Mgr must be defined before class Screen
friend Window_Mgr&
Window_Mgr::relocate(Window_Mgr::index, Window_Mgr::index, Screen&);
};
Friend Declaration and Scope
To make a member function a friend, the class containing that member must have been defined. On the other hand, a class or nonmember function need not have been declared to be made a friend. (Only member function need the class to be defined.)
A friend declaration introduces the named class or nonmember function into the surrounding scope. Moreover, a friend function may be defined inside the class. The scope of the function is exported to the scope enclosing the class definition.
Class names and functions (definitions or declarations) introduced in a friend can be used as if they had been previously declared:
class X {
friend class Y;
friend void f() { /* ok to define friend in the class body */ }
};
class Z {
Y *ymem; //ok: declaration for class Y introduced by friend in X
void g() { return ::f(); } //ok: declaration of f introduced by X
};
Overloaded Functions and Friendship
A class must declare as a friend each function in a set of overloaded functions that it wishes to make a friend.E.G. P 467
12.6 static Class Members
It is sometimes necessary for all the objects of a particular class type to access a global object.
Ordinarily, nonstatic data members exist in each object of the class type.
A static data member exists independently of any object of its class; each static data member is an object associated with the class, not with the objects of that class.
A class may define static data member or static member functions.
A static member function has no this parameter. It may directly access the static members of its class but may not directly use the nonstatic members.
Advantage of Using Class static MembersP 468
Defining static Members
A member is made static by prefixing the member declaration with the keyword static. The static members obey the normal public/private access rules. E.G.
class Account {
public:
void applying() {amount += amount * interestRate; }
static double rate() { return interestRate; }
static void rate( double ) ; //set new rate
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
};
Each object of the above class has two data members: owner and amount. Objects do not have data members that corresponding to static data members.
Using a Class static Member
A static member can be invoked directly from the class using scope operator; or indirectly through an object, reference or pointer.
Account ac1;
Account *ac2 = &ac1;
//equivalent ways to call the static member rate function
double rate;
rate = ac1.rate();
rate = act -> rate();
rate = Account::rate(); //directly from the class using the scope operator
12.6.1 static Member Functions
When we define a static member outside the class, we do not respecify the static keyword. The keyword appears only with the declaration inside the class body:
void Account::rate(double newRate)
{
interestRate = newRate;
}
static Functions Have No this Pointer
Referring to this either explicitly or implicitly inside a static function by using a nonstatic member is a compile-time error.
Because a static member is not part of any object, static member functions may not be declared as const.
12.6.2 static Data Members
static data members can be declared to be of any type. They can be consts, references, arrays, class types, and so forth.
static data members must be defined (exactly once) outside the class body. static members are not initialized through the class constructor(s) and instead should be initialized when they are defined.
The best way to ensure that the object is defined exactly once is to put the definition of static data members in the same file that contains the definitions of the class noninline member functions
static data members are defined by naming its type followed by the fully qualified name of the member:
//define and initialize static class member
double Account::interestRate = initRate();
Like other member definitions, the definition of a static member is in class scope once the member name is seen. So we can use the static member function named initRate directly without qualification as the initializer for rate.
Note the definition of interestRate is in the scope of the class and has access to the private member: initRate().
Integral const static Members Are Special
Ordinarily, class static members, like ordinary data members, cannot be initialized in the class body. (they are initialized when they are defined outside the class body)
One exception to this rule is a const static data member of integral type.
class Account {
private:
static const int period = 30;
}
When a const static data member is initialized in the class body, the data member must still be defined outside the class definition.
// definition with no initializer
// the initial value is specified inside the class definition
const int Accout::period;
static Members Are Not Part of Class Ojbects
We can use static data members in ways that would be illegal for nonstatic data members:
1. the type of static data member can be the class type of which it is a member. (nonstatic data member can only declared as a pointer or reference to an object of its class):
class Bar {
public:
/...
private:
static Bar mem1; //ok
Bar *mem2; //ok
Bar mem3; //error
};
2. static data member can be used as a default argument:
class Screen {
public:
Screen & clear(char = bkground);
private:
static const char bkground = '#';
};