Channels ▼

Andrew Koenig

Dr. Dobb's Bloggers

Moving an Object Does Not Destroy The Original

July 04, 2013

Last week and the week before that, I started discussing how a C++ compiler uses the type system to figure out when to move an object and when to copy it. Now I'd like to take a detour to discuss what moving an object actually does.

Recall first that objects are generally moved instead of copied because the originals are about to go away. When I learned this fact, I thought at first that moving an object should destroy the original. For example:

 
Thing make_Thing();
     
// …
     
Thing t = make_Thing();

In the last line of this example, the result returned by make_Thing is used to initialize the variable t, and is not used again after that. Therefore, that result can be moved instead of copied. Surely it makes sense to destroy the result as part of the move process!

Because of this reasoning, I was surprised to learn that that is not what happens. Instead, whenever a program moves an object, it must leave that object with a valid value so that it can be destroyed later. In general, this rule causes additional overhead, because it means that moving an object must typically install a new value in the original object, and all that happens to that new value is that it is destroyed later.

What I learned surprised me because in this particular example, the actual behavior seems to add overhead. As part of moving the temporary Thing returned by make_Thing into t, the generated code gives a new value to the temporary Thing, and then destroys that temporary Thing. Why bother? Why not just destroy the temporary Thing immediately?

In principle, I think it would have been possible to make C++ work that way. However, the way C++ actually does things makes life easier for C++ developers in an important way: It allows objects to be moved explicitly. For example:

 
     Thing make_Thing();
     void process_Thing(Thing);

Assume that the process_Thing function really does accept a Thing by value (i.e., type Thing) rather than by reference to const (i.e., type const Thing&). You might want to write code along these lines:

 
     Thing t = make_Thing();
     // …
     process_Thing(t);

Suppose you know that t is not going to be used again after passing it to process_Thing. If you were able to write

 
process_Thing(make_Thing());
 

then the compiler would move the result of make_Thing to Process_Thing. Unfortunately, you want to work on t between when you create it and when you pass it to process_Thing, so you can't write the code this way.

In cases such as this, C++ allows you to write

 
process_Thing(std::move(t));
 

When you do this, you are promising to the compiler that you are not going to use t again, so the compiler can move t rather than copying it.

You may ask: Why can't the compiler recognize std::move as a special case that causes t to be destroyed? It could notice this statement and simply not destroy t afterward, knowing that std::move had already done so.

The trouble is that the call to process_Thing is a statement, which means that it can be made conditional:

Thing t = make_Thing();
// …
               
if (…)
   process_Thing(std::move(t));

Depending on the condition in the if statement, it is now somewhere between difficult and impossible for the compiler to figure out whether std::move(t) has been called. If calling std::move(t) were to destroy t, the compiler would not know whether it was safe to destroy t at the end of the block containing this statement. So by requiring an object to have a valid value after its contents have been moved elsewhere, C++ makes it unnecessary for the compiler to track at runtime whether the object has been destroyed.

In short, after you move an object's contents to a new object:

  • The new object contains the same value as the original object did before the move; and
  • The original object contains a valid value that can (and should) be destroyed at the end of the original object's lifetime.

Notice that these rules say nothing about what the original object's value should be after the move. In practice, there are three common strategies, each of which is particularly easy to implement in some circumstances:

  1. The original object takes on an empty value of some kind.
  2. The original object retains its former value, effectively implementing move as copy.
  3. The original object gets the former value of the new object, effectively implementing move as swap.

This third possibility may be surprising, but consider that we know that the original object will be destroyed eventually. If moving an object t to another object s must overwrite the previous value of s, it may turn out to be more convenient to implement this operation by swapping the contents of s with those of t, knowing that t (which, after the swap, holds the original value of s) will be destroyed eventually.

In short, the C++ move operation may be surprising in that it does not destroy the original. However, along with that behavior comes several nice properties:

  • The compiler does not have to figure out whether an object might have been destroyed before the end of its natural life.
  • It is always possible to use a copy operation in place of a move operation; i.e., to implement move as copy.
  • It is always possible to use a swap operation in place of a move operation' i.e., to implement move as swap.

Next week we'll shift our focus back to the type system and take a look at how the std::move function might work.

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