String and string both use dynamic memory allocation. Does that mean you need to write a copy constructor and assignment operator for the Magazine class? No—at least not in itself. The default memberwise copying and assignment behavior does have some smarts. If you copy or assign one Magazine object to another, memberwise copying uses the copy constructors and assignment operators defined for the member types. That is, the String copy constructor will be used to copy the title member from one Magazine object to another, the String assignment operator will be used to assign the title member of one Magazine object to another, and so on. Things get more complicated, however, if the Magazine class needs a copy constructor and assignment operator for some other class member. In that case, those functions have to call the String and string copy constructors and assignment operators explicitly. But that’s a tale for Chapter 13, “Class Inheritance.”
Observations About Returning Objects
When a member function or standalone function returns an object, you have choices. The function could return a reference to an object, a constant reference to an object, an object, or a constant object. By now, you’ve seen examples of all but the last, so it’s a good time to review these options.
Returning a Reference to a const Object
The usual reason for using a const reference is efficiency, but there are restrictions on when this choice can be used. If a function returns an object that is passed to it, either by object invocation or as a method argument, you can increase the efficiency of the method by having it return a reference. For example, suppose you wanted to write a function Max() that returned the larger of two Vector objects, where Vector is the class developed in Chapter 11. The function would be used in this manner:
Vector force1(50,60);
Vector force2(10,70);
Vector max;
max = Max(force1, force2);
Either of the following two implementations would work:
// version 1
Vector Max(const Vector & v1, const Vector & v2)
{
if (v1.magval() > v2.magval())
return v1;
else
return v2;
}
// version 2
const Vector & Max(const Vector & v1, const Vector & v2)
{
if (v1.magval() > v2.magval())
return v1;
else
return v2;
}
There are three important points here. First, recall that returning an object invokes the copy constructor, whereas returning a reference doesn’t. Thus Version 2 does less work and is more efficient. Second, the reference should be to an object that exists when the calling function is executing. In this example, the reference is to either force1 or force2, and both are objects defined in the calling function, so this requirement is met. Third, both v1 and v2 are declared as being const references, so the return type has to be const to match.
Returning a Reference to a Non-const Object
Two common examples of returning a non-const object are overloading the assignment operator and overloading the << operator for use with cout. The first is done for reasons of efficiency, and the second for reasons of necessity.
The return value of operator=() is used for chained assignment:
String s1("Good stuff");
String s2, s3;
s3 = s2 = s1;
In this code, the return value of s2.operator=(s1) is assigned to s3. Returning either a String object or a reference to a String object would work, but, as with the Vector example, using a reference allows the function to avoid calling the String copy constructor to create a new String object. In this case, the return type is not const because the operator=() method returns a reference to s2, which it does modify.
The return value of operator<<() is used for chained output:
String s1("Good stuff");
cout << s1 << "is coming!";
Here, the return value of operator<<(cout, s1) becomes the object used to display the string "is coming!". Here, the return type has to be ostream & and not just ostream. Using an ostream return type would require calling the ostream copy constructor, and, as it turns out, the ostream class does not have a public copy constructor. Fortunately, returning a reference to cout poses no problems because cout is already in scope in the calling function.
Returning an Object
If the object being returned is local to the called function, then it should not be returned by reference because the local object has its destructor called when the function terminates. Thus, when control returns to the calling function, there is no object left to which the reference can refer. In these circumstances, you should return an object, not a reference. Typically, overloaded arithmetic operators fall into this category. Consider this example, which uses the Vector class again:
Vector force1(50,60);
Vector force2(10,70);
Vector net;
net = force1 + force2;