Jump to content

More C Idioms/Move Constructor

From Wikibooks, open books for an open world

Move Constructor

[edit | edit source]

Intent

[edit | edit source]

To transfer the ownership of a resource held by an object to another object in C 03. Note: In C 11, move-constructors are implemented using the built-in rvalue reference feature.

Also Known As

[edit | edit source]
  • Colvin-Gibbons trick
  • Source and Sink Idiom

Motivation

[edit | edit source]

Some objects in C exhibit so-called move semantics. For example, std::auto_ptr. In the code that follows, auto_ptr b ceases to be useful after the creation of object a.

std::auto_ptr <int> b (new int (10));
std::auto_ptr <int> a (b);

The copy constructor of auto_ptr modifies its argument, and hence it does not take a const reference as a parameter. It poses no problem in the above code because object b is not a const object. But it creates a problem when a temporary is involved.

When a function returns an object by value and that returned object is used as an argument to a function (for example to construct another object of the same class), compilers create a temporary of the returned object. These temporaries are short lived and as soon as the statement is executed, the destructor of the temporary is called. The temporary therefore owns its resources for a very short time. The problem is that temporaries are quite like const objects (it makes little sense to modify a temporary object). Therefore, they can not bind to a non-const reference and as a result, can not be used to call the constructor taking a non-const reference. A move constructor can be used in such cases.

Solution and Sample Code

[edit | edit source]
namespace detail {

template <class T>  
struct proxy
{
  T *resource_;
};

} // detail

template <class T>
class MovableResource
{
  private:
    T * resource_;

  public:
    explicit MovableResource (T * r = 0) : resource_(r) { }
    ~MovableResource() throw() { delete resource_; }   // Assuming std::auto_ptr like behavior.

    MovableResource (MovableResource &m) throw () // The "Move constructor" (note non-const parameter)
      : resource_ (m.resource_)
    {
      m.resource_ = 0; // Note that resource in the parameter is moved into *this.
    }

    MovableResource (detail::proxy<T> p) throw () // The proxy move constructor
      : resource_(p.resource_)
    {
      // Just copying resource pointer is sufficient. No need to NULL it like in the move constructor.
    }

    MovableResource & operator = (MovableResource &m) throw () // Move-assignment operator (note non-const parameter)
    {
      // copy and swap idiom. Must release the original resource in the destructor.
      MovableResource temp (m); // Resources will be moved here.
      temp.swap (*this);
      return *this;
    }

    MovableResource & operator = (detail::proxy<T> p) throw ()
    {
      // copy and swap idiom. Must release the original resource in the destructor.
      MovableResource temp (p);
      temp.swap(*this);
      return *this;
    }

    void swap (MovableResource &m) throw ()
    {
      std::swap (this->resource_, m.resource_);
    }

    operator detail::proxy<T> () throw () // A helper conversion function. Note that it is non-const
    {
      detail::proxy<T> p;
      p.resource_ = this->resource_;
      this->resource_ = 0;     // Resource moved to the temporary proxy object.
      return p;
    }
};

The move constructor/assignment idiom plays an important role in the code snippet shown next. The function func returns the object by value i.e., a temporary object is returned. Though MovableResource does not have any copy-constructor, the construction of local variable a in main succeeds, while moving the ownership away from the temporary object.

MovableResource<int> func()
{
  MovableResource<int> m(new int());
  return m;
}
int main()
{
  MovableResource<int> a(func()); // Assuming this call is not return value optimized (RVO'ed).
}

The idiom uses a combination of three interesting and standard features of C .

  • A copy-constructor need not take its parameter by const reference. The C standard provides a definition of a copy-constructor in section 12.8.2. Quoting -- A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments. -- End quote.
  • A sequence of conversions via the detail::proxy<T> object is identified automatically by the compiler.
  • Non-const functions can be called on temporary objects. For instance, the conversion function operator detail::proxy<T> () is non-const. This member conversion operator is used to modify the temporary object.

The compiler seeks a copy-constructor to initialize object a. There is a copy-constructor defined but it accepts its parameter by non-const reference. The non-const reference does not bind to the temporary object return by function func. Therefore, the compiler looks for other options. It identifies that a constructor that accepts a detail::proxy<T> object is provided. So it tries to identify a conversion operator that converts the MovableResource object to detail::proxy<T>. As a matter of fact, such a conversion operator is also provided (operator detail::proxy<T>()). Note also that the conversion operator is non-const. That's not a problem because C allows invoking non-const functions on temporaries. Upon invocation of this conversion function, the local MovableResource object (m) loses its resource ownership. Only the proxy<T> object knows the pointer to T for a very brief period of time. Subsequently, the converting constructor (the one that takes detail::proxy<T> as a parameter) successfully obtains the ownership (object a in main).

Let's look into the details of how temporary objects are created and used. In fact, the above steps are executed not once but twice in exactly the same way. First to create a temporary MovableResource object and later to create the final object a in main. The second exceptional rule of C comes into play when a is being created from the temporary MovableResource object. The conversion function (operator detail::proxy<T>()) is called on the temporary MovableResource object. Note that it is non-const. A non-const member function can be called on a temporary object unlike real const objects. Section 3.10.10 in C ISO/IEC 14882:1998 standard clearly mentions this exception. More information on this exception can be found here. The conversion operator happens to be a non-const member function. Therefore, the temporary MovableResource object also loses its ownership as described above. Several temporary proxy<T> objects also are created and destroyed when the compiler figures out the right sequence of conversion functions. It is possible that the compiler might eliminate certain temporaries using return value optimization (RVO). To observe all the temporaries, use --no-elide-constructors option to the gcc compiler.

This implementation of the move constructor idiom is useful to transfer the ownership of resources (e.g., heap-allocated memory) in and out of a function. A function that accepts MovableResource by-value serves as a sink function because the ownership is transferred to a function deeper inside the call-stack whereas a function that returns MovableResource by-value serves as source because the ownership is moved to an outer scope. Hence the name source-and-sink idiom.

Safe Move Constructor

Although the source-and-sink idiom is quite useful at the boundaries of a function call, there are some undesirable side-effects of the above implementation. Particularly, simple expressions that look like copy-assignment and copy-initialization do not make copies at all. For instance, consider the following code:

MovableResource mr1(new int(100));
MovableResource mr2;
mr2 = mr1;

MovableResource object mr2 looks like it has been copy-assigned from mr1. However, it silently steals the underlying resource from mr1 leaving it behind as if it is default initialized. The mr1 object no longer owns the heap allocated integer. Such behavior is quite surprising to most programmers. Moreover, generic code written assuming regular copy semantics will likely have very undesirable consequences.

The semantics mentioned above are commonly known as Move Semantics. It is a powerful optimization. However, part of the problem of this optimization, implemented in the above form, is the lack of error messages during compilation of assignment and copy-initialization of expressions that look like a copy but in fact perform a move. Specifically, implicitly copying one MovableResource from another named MovableResource (e.g., mr1) is unsafe and therefore should be indicated by an error.

Note that stealing resources from a temporary object is perfectly acceptable because they do not last long. The optimization is meant to be used on rvalues (temporary objects). Safe move constructor idiom is a way of achieving this distinction without language-level support (rvalue references in C 11).

The safe move constructor idiom prevents implicit move semantics from named objects by declaring private constructor and assignment operator functions.

private:
  MovableResource (MovableResource &m) throw ();
  MovableResource & operator = (MovableResource &m) throw ();

Key thing to note here is that the parameter types are non-const. They bind to named objects only and not to temporary objects. This little change alone is sufficient to disable implicit moves from named objects but allow moves from temporary objects. The idiom also provides a way to do explicit move from a named object, if desired.

template <class T>
MovableResource<T> move(MovableResource<T> & mr) throw() // Convert explicitly to a non-const reference to rvalue
{
  return MovableResource<T>(detail::proxy<T>(mr));
}
MovableResource<int> source()
{
  MovableResource<int> local(new int(999));
  return move(local);
}
void sink(MovableResource<int> mr)
{
  // Do something with mr. mr is deleted automatically at the end.
}
int main(void)
{
  MovableResource<int> mr(source()); // OK
  MovableResource<int> mr2;
  mr2 = mr;                          // Compiler error
  mr2 = move(mr);                    // OK
  sink(mr2);                         // Compiler error
  sink(move(mr2));                   // OK
}

The move function above is also quite idiomatic. It must accept the parameter as a non-const reference to the object so that it binds to l-values only. It turns a non-const MovableResource lvalue (named object) into an rvalue (temporary object) via conversion to detail::proxy. Two explicit conversions are necessary inside the move function to avoid certain language quirks. Ideally, the move function should be defined in the same namespace as that of MovableResource class so that the function can be looked up using argument dependent lookup (ADL).

Function source returns a local MovableResource object. Because it is a named object, move must be used to turn it into an rvalue before returning. In main, first MovableResource is initialized directly from the temporary returned by func. Simple assignment to another MovableResource fails with a compiler error but explicit move works. Similarly, implicit copy to the sink function does not work. Programmer must explicitly express its interest in moving the object to the sink function.

Finally, it is also important that the key functions (construction from and to detail::proxy) be non-throwing to guarantee at least basic exception guarantee. No exceptions should be thrown in the meanwhile, otherwise there will be resource leaks.

C 11 provides rvalue references, which support language-level move semantics, eliminating the need for the Move Constructor idiom.

Historical Alternatives

An old (but inferior) alternative is to use a mutable member to keep track of ownership to sneak past the const-correctness checks of the compiler. The following code shows how the MovableResource may be implemented alternatively.

template<class T>
class MovableResource
{
  mutable bool owner;
  T* px;

public:
  explicit MovableResource(T* p=0)
       : owner(p), px(p) {}

  MovableResource(const MovableResource& r)
       : owner(r.owner), px(r.release()) {}

  MovableResource & operator = (const MovableResource &r)
  {
    if ((void*)&r != (void*)this)
    {
      if (owner)
        delete px;
      owner = r.owner;
      px = r.release();
    }
    return *this;
  }

  ~MovableResource() { if (owner) delete px; }
  T& operator*() const { return *px; }
  T* operator->() const { return px; }
  T* get()    const { return px; }
  T* release()  const { owner = false; return px; } // mutable 'ownership' changed here.
};

This technique, however, has several disadvantages.

  • The copy constructor and copy-assignment operators do not make logical copies. On the contrary, they transfer the ownership from right hand side object to *this. This fact is not reflected in the interface of the second implementation of MovableResource. The first MovableResource class accepts a MovableResource object by non-const reference, which prevents the use of the object in contexts where a copy is expected.
  • The Const auto_ptr idiom, which is C 03's way of preventing transfer of ownership is simply not possible with the second implementation of the MovableResource class. This idiom depends on the fact that const auto_ptr objects cannot bind to the non-const reference parameters of the move-constructor and move-assignment operators. Making the parameters const references defeats the purpose.
  • The boolean flag 'owner' increases the size of the structure. The increase in size is substantial (essentially double) for classes like std::auto_ptr, which otherwise contain just a pointer. Doubling of the size is due to compiler-enforced alignment of data.

Known Uses

[edit | edit source]
[edit | edit source]

References

[edit | edit source]