The value being returned is not force1, which should be left unaltered by the process, nor force2, which should also be unaltered. Thus the return value can’t be a reference to an object that is already present in the calling function. Instead, the sum is a new, temporary object computed in Vector::operator+(), and the function shouldn’t return a reference to a temporary object either. Instead, it should return an actual vector object, not a reference:
Vector Vector::operator+(const Vector & b) const
{
return Vector(x + b.x, y + b.y);
}
There is the added expense of calling the copy constructor to create the returned object, but that is unavoidable.
One more observation: In the Vector::operator+() example, the constructor call Vector(x + b.x, y + b.y) creates an object that is accessible to the operator+() method; the implicit call to the copy constructor produced by the return statement, however, creates an object that is accessible to the calling program.
Returning a const Object
The preceding definition of Vector::operator+() has a bizarre property. The intended use is this:
net = force1 + force2; // 1: three Vector objects
However, the definition also allows you to use the following:
force1 + force2 = net; // 2: dyslectic programming
cout << (force1 + force2 = net).magval() << endl; // 3: demented programming
Three questions immediately arise. Why would anyone write such statements? Why are they possible? What do they do?
First, there is no sensible reason for writing such code, but not all code is written for sensible reasons. People, even programmers, make mistakes. For instance, if the comparison operator==() were defined for the Vector class, you might mistakenly type
if (force1 + force2 = net)
instead of this:
if (force1 + force2 == net)
Also programmers tend to be ingenious, and this can lead to ingeniously adventurous mistakes.
Second, this code is possible because the copy constructor constructs a temporary object to represent the return value. So in the preceding code, the expression force1 + force2 stands for that temporary object. In Statement 1, the temporary object is assigned to net. In Statements 2 and 3, net is assigned to the temporary object.
Third, the temporary object is used and then discarded. For instance, in Statement 2, the program computes the sum of force1 and force2, copies the answer into the temporary return object, overwrites the contents with the contents of net, and then discards the temporary object. The original vectors are all left unchanged. In Statement 3, the magnitude of the temporary object is displayed before the object is deleted.
If you are concerned about the potential for misuse and abuse created by this behavior, you have a simple recourse: Declare the return type as a const object. For instance, if Vector::operator+() is declared to have return type const Vector, then Statement 1 is still allowed but Statements 2 and 3 become invalid.
In summary, if a method or function returns a local object, it should return an object, not a reference. In this example, the program uses the copy constructor to generate the returned object. If a method or function returns an object of a class for which there is no public copy constructor, such as the ostream class, it must return a reference to an object. Finally, some methods and functions, such as the overloaded assignment operator, can return either an object or a reference to an object. In this example, the reference is preferred for reasons of efficiency.
Using Pointers to Objects
C++ programs often use pointers to objects, so let’s get in a bit of practice. Listing 12.6 uses array index values to keep track of the shortest string and of the first string alphabetically. Another approach is to use pointers to point to the current leaders in these categories. Listing 12.7 implements this approach, using two pointers to String. Initially, the shortest pointer points to the first object in the array. Each time the program finds an object with a shorter string, it resets shortest to point to that object. Similarly, a first pointer tracks the alphabetically earliest string. Note that these two pointers do not create new objects; they merely point to existing objects. Hence they don’t require using new to allocate additional memory.
For variety, the program in Listing 12.7 uses a pointer that does keep track of a new object:
String * favorite = new String(sayings[choice]);
Here the pointer favorite provides the only access to the nameless object created by new. This particular syntax means to initialize the new String object by using the object sayings[choice]. That invokes the copy constructor because the argument type for the copy constructor (const String &) matches the initialization value (sayings[choice]). The program uses srand(), rand(), and time() to select a value for choice at random.
Listing 12.7. sayings2.cpp