Home Articles Books Downloads FAQs Tips

What's wrong with this code? Volume #1


The assignment operator

The assignment operator for the Foo class contains a common programming mistake. Assignment operators should almost always pass their arguments by const reference. A lot of programmers leave out the const part, and just pass by reference. When you forget the const keyword, the code will usually continue to compile and run correctly. However, the code example in this issue demonstrates a situation where const cannot be omitted. Recall that the original assignment operator looked like this:

Foo& operator=( Foo &rhs)
{
    cout << "assignment" << endl;
    return *this;
}

The compilation error occurred where an object was being assigned a return value from a function.

foo1 = GetAFoo();   // generates compiler error

So why does this assignment fail to compile, when the assignment to foo2 compiles without error?

foo2 = foo1;        // compiles OK

The difference is that GetAFoo returns a Foo object by value. Return objects are temporary objects, and temporary objects are const objects. You cannot modify a temporary object. foo1 on the other hand, is not a temporary object, which means that it is not a const object.

The assignment operator for Foo takes a non-const object by reference. The compiler will not pass const objects to a function that take references to non-const objects. Why? Because when you pass a value by reference, the function has the power to alter the original object. The compiler must ensure that const objects are not changed.

Because the foo assignment operator did not list the const keyword, the compiler will not try to use it for temporary objects. Since no other suitable assignment operator is available that takes const parameters, the compiler issues an error.

The solution to this problem is to change the assignment operator so it takes a constant reference.

class Foo
{
public:
    ...
    Foo& operator=(const Foo &rhs)
    {
        cout << "assignment" << endl;
        return *this;
    }
};

Once this change is made to the Foo class, the code example compiles and runs without error. Here is the output of the program after making this correction. Note that the comments were added to explain the output.

default constructor         // construct foo1
default constructor         // construct foo2
default constructor         // construct foo inside GetAFoo
copy construction           // copy foo to temporary return object
destructor                  // destory local foo in GetAFoo
assignment                  // assign temp return object to foo1
destructor                  // destory temp object
assignment                  // assign foo1 to foo2
destructor                  // end of main, destroy foo1
destructor                  // and destroy foo2.

In addition to fixing the compilation error, there are a couple of ways to streamline the code. The first change involves the GetAFoo function, which originally looked like this:

Foo GetAFoo()
{
    Foo foo;
    return foo;
}

As it stands, this code will construct two Foo objects. The first is the foo variable that you see in the code. The second object is the return object for the function. The foo variable is constructed with the default constructor for the Foo class. The temporary return object is created with the copy constructor. The debug statements from the program shows how and when these two objects are created and destroyed.

...
default constructor  // creates foo variable
copy construction    // construct temp return object
destructor           // destroy foo because it is out of scope
assignment           // call assignment operator and pass temp object
destructor           // delete temp object
...

As this output shows, calling the GetAFoo function results in the construction and deletion of two Foo objects. Because GetAFoo does not use the local foo variable for anything, we can streamline the function by eliminating the variable. The new and improved GetAFoo function looks like this:

Foo GetAFoo()
{
    return Foo();
}

Note Note:
This optimization is called the Return Value Optimization. Compilers do not have to provide this optimization, but most of them do. All versions of C++Builder implement this optimization. Scott Meyers discusses this optimization technique in his book More Effective C++.

This code eliminates the temporary variable, the copy constructor that was needed to initialize it, and the destructor that was needed to deallocate it. The debug output from the streamlined function looks like this.

...
default constructor   // create return object with default constructor
assignment            // pass return object to assignment operator
destructor            // destroy return object
...

There is one other place where the code can be improved. Notice how the main function creates and initializes the two Foo objects.

int main()
{
    Foo foo1;           // default constructor
    Foo foo2;           // default constructor
    foo1 = GetAFoo();   // Assignment
    foo2 = foo1;        // Assignment
    return 0;
}

When main is structured like this, four function calls are needed to initialize two objects. When can reduce this to two calls if by eliminating the use of the default constructors.

int main()
{
    Foo foo1(GetAFoo()); // copy constructor
    Foo foo2(foo1);      // copy constructor
    return 0;
}

Ironically, this code enhancement eliminates the use of the assignment operator, and the assignment operator was the original cause of our compiler errors. After making these improvements to the code, the final source should look like this:

#include <iostream>

using namespace std;

class Foo
{
public:
    Foo()
    {
        cout << "default constructor" << endl;
    }
    Foo(const Foo &)
    {
        cout << "copy construction" << endl;
    }

    ~Foo()
    {
        cout << "destructor" << endl;
    }

    Foo& operator=(const Foo &rhs)
    {
        cout << "assignment" << endl;
        return *this;
    }
};


Foo GetAFoo()
{
    return Foo();
}

int main()
{
    Foo foo1(GetAFoo());
    Foo foo2(foo1);
    return 0;
}

That does it for this edition of "What's wrong with this code?" Unless of course, you can find more ways to improve the final version of the code. Since this is the first edition of w3TC, I would be interested in your feedback. Do you like these sorts of articles, or did this article suck as much as the code that was in it? Feedback is welcome. If you have ideas for future articles, I would be happy to hear them. Just click the email link at the bottom of the page.



Copyright © 1997-2002 by Harold Howe.
All rights reserved.