To start with, this article is not actually my own words. It is a small section of the fantastic book
"Efective C++: 55 Specific Ways tom Improve Your Programs and Designs", by
Scott Meyers, and I thought it warranted an article being made for it as it gives some very useful information.
Try not to get held up on the first few sentences as this is mid flow in the chapter...
For almost everything else, the responsibility for initialization falls on constructors. The rule there is simple: make sure that all constructors initialize everything in the object.
The rule is easy to follow, but it’s important not to confuse assignment with initialization. Consider a constructor for a class representing entries in an address book:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
Class PhoneNumber { … };
Class ABEntry { // ABEntr = “Address Book Entry”
public:
ABEntry(const std::String& name, const std::string& address,
const std::list<PhoneNumber>& phones);
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};
ABEntry(const std::String& name, const std::string& address,
const std::list<PhoneNumber>& phones)
{
theName = name; // these are all assignments,
theAddress = address; // no initializations
thePhones = phones;
numTimesConsulted = 0;
}
| |
This will yield ABEntry objects with the values you expect, but it’s still not the best approach. The rules of C++ stipulate that data members of an object are initialized
before the body of a constructor is entered. Inside the ABEntry constructor, theName, theAddress, and thePhones aren’t being initialized, they’re being assigned. Initialization took place earlier – when their default contructors were automatically called prior to entering the body of the ABEntry constructor. This isn’t true for numTimesConsulted, because it’s a built-in type. For it, there’s no guarantee it was initialized at all prior to its assignment.
A better way to write the ABEntry constructor is to use the member initialization list instead of assignments:
1 2 3 4 5 6 7
|
const std::String& name, const std::string& address,
const std::list<PhoneNumber>& phones)
: theName(name),
theAddress(address), // these are now all initializations
thePhones(phones),
numTimesConsulted(0)
{} // the ctor body is now empty
| |
This constructor yields the same end result as the one above, but it will often be more efficient. The assignment-based version first called default constructors to initialize theName, theAddress, and thePhones, then promptly assigned new values on top of the default-constructed ones. All the work performed in those default constructions was therefore wasted. The member initialization list approach avoids that problem, because the arguments in the initilization list approach avoids that problem, because the arguments in the initialization list are used as constructor arguments for the various data members. In this case, theName is copy-constructed from name, theAddress is copy-constructed from address, and thePhones is copy-constructed from phones. For most types, a single call to a copy constructor is more efficient – sometimes
much more efficient – than a call to the default constructor followed by a call to the copy assignemnet operator.
For objects of built-in type like numTimesConsulted, there is no difference in cost between initialization and assignment, but for consistence, it’s often best to initialize everything via member initialization. Similarly, you cn use the member initilzation list even when you want to default-construct a data member; just specify nothing as an initialization argument.