Professional C__ - Marc Gregoire [87]
{
return mString;
}
Now consider the following code:
SpreadsheetCell myCell2(5);
string s1;
s1 = myCell2.getString();
When getString() returns mString, the compiler actually creates an unnamed temporary string object by calling a string copy constructor. When you assign this result to s1, the assignment operator is called for s1 with the temporary string as a parameter. Then, the temporary string object is destroyed. Thus, the single line of code invokes the copy constructor and the assignment operator (for two different objects).
In case you’re not confused enough, consider this code:
SpreadsheetCell myCell3(5);
string s2 = myCell3.getString();
In this case, getString() still creates a temporary unnamed string object when it returns mString. But now s2 gets its copy constructor called, not its assignment operator.
With move semantics from C++11, the compiler can use a move constructor instead of a copy constructor to return mString from getString(). This is much more efficient. Move semantics is discussed in Chapter 9.
If you ever forget the order in which these things happen or which constructor or operator is called, you can easily figure it out by temporarily including helpful output in your code or by stepping through it with a debugger.
Copy Constructors and Object Members
You should also note the difference between assignment and copy constructor calls in constructors. If an object contains other objects, the compiler-generated copy constructor calls the copy constructors of each of the contained objects recursively. When you write your own copy constructor, you can provide the same semantics by using a ctor-initializer, as shown previously. If you omit a data member from the ctor-initializer, the compiler performs default initialization on it (a call to the 0-argument constructor for objects) before executing your code in the body of the constructor. Thus, by the time the body of the constructor executes, all object data members have already been initialized.
For example, you could write your copy constructor like this:
SpreadsheetCell::SpreadsheetCell(const SpreadsheetCell& src)
: mString(src.mString)
{
mValue = src.mValue;
}
However, when you assign values to data members in the body of the copy constructor, you are using the assignment operator on them, not the copy constructor, because they have already been initialized, as described previously.
In this example, mString is initialized using the copy constructor, while mValue is assigned to using the assignment operator.
SUMMARY
This chapter covered the fundamental aspects of C++’s facilities for object-oriented programming: classes and objects. It first reviewed the basic syntax for writing classes and using objects, including access control. Then, it covered object life cycles: when objects are constructed, destructed, and assigned, and what methods those actions invoke. The chapter included details of the constructor syntax, including ctor-initializers and initializer-list constructors, and introduced the notion of copy assignment operators. It also specified exactly which constructors the compiler writes for you, and under what circumstances, and explained that default constructors take no arguments.
For some of you, this chapter was mostly review. For others, it hopefully opened your eyes to the world of object-oriented programming in C++. In any case, now that you are proficient with objects and classes, read Chapter 7 to learn more about their tricks and subtleties.
Chapter 7
Mastering Classes and Objects
WHAT’S IN THIS CHAPTER?
How to use dynamic memory allocation in objects
The different kinds of data members you can have (static, const, reference)
The different kinds of methods you can implement (static, const, inline)
The details of method overloading
How to work with default parameters
How to use nested classes
How to make classes friends of other classes
What operator overloading is
How to write separate interface and implementation classes.
Chapter 6 started the discussion on classes and