Channels ▼

Andrew Koenig

Dr. Dobb's Bloggers

Object Swapping Part 4: How Class Operations Relate

April 05, 2012

The fundamental operations that a class offers to its users are construction, destruction, copying, and assignment. Construction usually includes copy construction and at least one way of constructing a container that is not a copy. As a result, a container will usually include at least the corresponding four members:


  • At least two constructors, one of which is a copy constructor
  • Destructor
  • Copy-assignment operator

These members' implementations typically intertwine. For example, it is not unusual to think about assignment as first clearing the destination and then copying the source into it. This way of thinking leads to an implementation strategy that many of you have probably seen:

 
     class Thing {
     public:
           Thing() { init(); }
           Thing(const Thing& t): { copy(t); }
           Thing& operator=(const Thing& t) {
                if (this != &t) {
                     destroy();
                      copy(t);
                }
                return *this;
           }
           ~Thing() { destroy(); }
     private:
           void init();
           void destroy();
           void copy(const Thing&);
     };
 

Our users know about four operations: A copy constructor, a default constructor, a copy-assignment operator, and a destructor. These four operations are implemented in terms of three operations, which we have called init, destroy, and copy.

It should not be hard to figure out how these three operations should work. In particular, both init and copy must assume that there is nothing of value in the object's contents at the time at which they are called. In contrast, destroy assumes that there is something of value in the object, so that it has something to deallocate.

This strategy has two fundamental problems: It is not exception safe, and it is not easy to extend to include move construction or move assignment.

The problem with exception safety is that if copy throws an exception, it is possible that some but not all of the contents of the Thing have been filled. In order to avoid having the Thing's destructor destroy data twice, copy has to keep track of its progress and arrange, in case it is exited by an exception, to leave the Thing in a destructible state.

That requirement is bad enough — but now look at the copy-assignment operator. If copy (inside the copy-assignment operator) throws an exception, the contents of the left-hand side have already been destroyed. In other words, if Thing assignment throws an exception, it is possible for the Thing to have a value that is neither its old nor its intended new value.

Now let's think about what we have to do to add move and move assignment. In both cases, we wish to avoid having to create and then destroy a temporary copy of our Thing, so we need a way to transfer the contents of one Thing to another without copying those contents. Unfortunately, none of our three operations makes this transfer possible.

Now let's think of about last week's article, in which I observed that it is possible to implement move assignment as swap. If we add a swap operation to our class, we can immediately use it to implement move assignment. Let's do that before we think about plain move:

     class Thing {
     public:
           // All members as before, except that we add
                           // the following move-assignment operator:
           Thing& operator=(Thing&& t) { swap(t); }
     private:
           // All members as before, except that we add:
                           void swap(Thing& t);
             };
 

We don't even need to bother to clear out the contents of t, because we know that this variant of operator= will be called only if its argument is about to go away soon anyway. That's the only kind of argument that is passed to an rvalue reference parameter.

Now let's think about plain move. Here we have to avoid actually making a copy of the source, but we want the source's original contents to wind up in our object. The way to do that is to clear out our object and then swap its contents with the source:

 
             // Add one more member to Thing:
             Thing(Thing&& t) { init(); swap(t); }

What we've seen so far is that adding a swap operation to our implementation toolkit makes move and move assignment trivial to implement. Now let's look at copy assignment.

We said before that the problem with copy assignment is that if copy throws an exception, it leaves our object in an unknown state. What if instead of clearing our object and then copying into it, we created the copy in a temporary object and then swapped it in? Our copy assignment operator might then look like this:

 
     Thing& operator=(const Thing& t) {
           Thing t_copy = t;
           swap(t_copy);
           return *this;
     }
 

We make a copy of t in t_copy. If that copy throws an exception, t_copy is destroyed, our own object is unchanged, and there's no harm done. So long as swap doesn't throw an exception, we are now ensured that our object will take on its new value. When operator= returns, the old value will be destroyed. We note in passing that we no longer need to test for self-assignment.

As it happens, we don't even need a separate local variable: It suffices to change operator= to use call by value. As this is the last version of our class this time around, we'll show you the whole thing:

 
     class Thing {
     public:
           // Default constructor
Thing() { init(); }
 
// Copy and move constructors
           Thing(const Thing& t): { copy(t); }
                          Thing(Thing&& t) { init(); swap(t); }
 
           // Copy and move assignment
     Thing& operator=(Thing t) {
           swap(t);
           return *this;
     }
           Thing& operator=(Thing&& t) { swap(t); }
 
           // Destructor
           ~Thing() { destroy(); }
 
     private:
           void init();
           void destroy();
           void copy(const Thing&);
           void swap(Thing&);
     };

We have implemented six user-interface operations — default construction, copy and move construction, copy and move assignment, and destruction — in terms of four underlying operations. Interestingly, these operations include swapping but not assignment. Evidently, in the world of class implementation, swapping is more fundamental.

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 


Video