What is copy elision and how does it optimize the copy-and-swap idiom?

The copy constructor exists to make copies. In theory when you write a line like:

CLASS c(foo());

The compiler would have to call the copy constructor to copy the return of foo() into c.

Copy elision is a technique to skip calling the copy constructor so as not to pay for the overhead.

For example, the compiler can arrange that foo() will directly construct its return value into c.

Here's another example. Let's say you have a function:

void doit(CLASS c);

If you call it with an actual argument, the compiler has to invoke the copy constructor so that the original parameter cannot be modified:

CLASS c1;
doit(c1);

But now consider a different example, let's say you call your function like this:

doit(c1 + c1);

operator+ is going to have to create a temporary object (an rvalue). Instead of invoking the copy constructor before calling doit(), the compiler can pass the temporary that was created by operator+ and pass that to doit() instead.


Here is an example:

#include <vector>
#include <climits>

class BigCounter {
 public:
   BigCounter &operator =(BigCounter b) {
      swap(b);
      return *this;
   }

   BigCounter next() const;

   void swap(BigCounter &b) {
      vals_.swap(b);
   }

 private:
   typedef ::std::vector<unsigned int> valvec_t;
   valvec_t vals_;
};

BigCounter BigCounter::next() const
{
   BigCounter newcounter(*this);
   unsigned int carry = 1;
   for (valvec_t::iterator i = newcounter.vals_.begin();
        carry > 0 && i != newcounter.vals_.end();
        ++i)
   {
      if (*i <= (UINT_MAX - carry)) {
         *i += carry;
      } else {
         *i += carry;
         carry = 1;
      }
   }
   if (carry > 0) {
      newcounter.vals_.push_back(carry);
   }
   return newcounter;
}

void someFunction()
{
    BigCounter loopcount;
    while (true) {
       loopcount = loopcount.next();
    }
}

In somefunction the line loopcount = loopcount.next(); benefits greatly from copy elision. If copy elision were not allowed, that line would require 3 invocations of the copy constructor and an associated call to a destructor. With copy elision being allowed, it can be reduced to 1 call of the copy constructor, the explicit one inside of BigCount::next() where newcounter is declared.

If operator = had been declared and defined like this:

BigCounter &BigCounter::operator =(const BigCounter &b) {
   BigCounter tmp(b);
   swap(tmp);
   return *this;
}

there would've had to have been 2 invocations of the copy constructor, even with copy elision. One to construct newcounter and the other to construct tmp. And without copy elision there would still be 3. That's why declaring operator = so its argument requires invoking the copy construct can be an optimization when using the 'copy and swap' idiom for the assignment operator. When the copy constructor is invoked for constructing an argument, its invocation may be elided, but if it's invoked to create a local variable, it may not be.