7
votes

I'm trying to learn move semantics well enough to introduce it to my students. I've been using highly simplified vector- or string-like classes that manage memory and whose members output messages to demonstrate their activity. I'm trying to develop a simple set of examples to show students.

Construction elision for RVO and elsewhere in gcc 4.7 and clang aggressively eliminates copy and move construction, so while I can easily see move assignment at work, the only time I've seen move construction at work is if I turn off construction elision in gcc 4.7 with -fno-elide-constructors.

An explicit copy construction statement

MyString newString(oldString);

will invoke the copy constructor even if elision is enabled. But something like

MyString newString(oldString1 + oldString2); 

doesn't invoke the move constructor because of the elision.

Anything explicitly using std::move won't make a simple example because explaining std::move has to come later.

So my question: Is there a simple code example that will invoke move construction even if copy/move constructors are being elided?

2
I'm not certain it can be done without std::move...Mooing Duck
"Anything explicitly using std::move won't make a simple example because explaining std::move has to come later." Um, why? std::move is how you move things explicitly. Move support is primarily about explicit moves, since most implicit moves can be caught with elision. You're doing anyone who's learning from this a disservice by hiding move from them.Nicol Bolas
@NicolBolas: because there are implicit moves, at least in theory. Think of OP asking for an intermediate level of optimization: oldString1+oldString2 triggers a copy (no optimization here), but the copy is moved into newString (optimization). In practice, this is hard to trigger because the compiler chooses the "copy elision" optimization over the "move the copy" optimization (with good reason).André Caron
I will have to explain std::move, but it would help if I could demonstrate move construction without having to first introduce std::move (whose explanation is a tad tricky).user1628444
@NicolBolas - sorry, I should have prefixed my comment above to show my comment was in response to yours.user1628444

2 Answers

7
votes

The simple example would be an argument to a function that is returned. The standard explicitly forbids eliding the move in this case (not that they could...):

std::vector<int> multiply( std::vector<int> input, int value ) {
   for (auto& i : input )
      i *= value;
   return input;
}

Additionally, you can explicitly request move construction for an even simpler although a bit more artificial example:

T a;
T b( std::move(a) );

Uhm... yet another that does not involve std::move (it can technically be elided, but most compilers will probably not):

std::vector<int> create( bool large ) {
   std::vector<int> v1 = f();
   std::vector<int> v2 = v1;       // modify both v1 and v2 somehow
   v2.resize( v2.size()/2 );
   if ( large ) {
      return v1;
   } else {
      return v2;
   }
}

While the optimizer can elide it by rewriting the code as:

std::vector<int> create( bool large ) {
   if ( large ) {
      std::vector<int> v1 = f();
      std::vector<int> v2 = v1;       // modify both v1 and v2 somehow
      v2.resize( v2.size()/2 );
      return v1;
   } else {
      std::vector<int> v1 = f();
      std::vector<int> v2 = v1;       // modify both v1 and v2 somehow
      v2.resize( v2.size()/2 );
      return v2;
   }
}

I pretty much doubt that the compiler will actually do it. Note that in each code path the object being returned in known before v1 and v2 are created, so the optimizer can locate the proper object in the return location after the rewrite.

The situations where copy/move can be elided are described in 12.8/31. If you manage to write code that does not fall into those categories and the type has a move constructor, the move constructor will be called.

1
votes

Hmmm, let's see:

  • MyString newString(oldString) is a copy. There's nothing to elide here; we really end up with two objects.

  • MyString newString(oldString1 + oldString2); copies from a temporary, so the copy can be elided and the concatenation is constructed directly in-place.

Here's a really terribly cheap example of un-elidable move construction:

MyString boo()
{
    MyString s("Hello");
    return std::move(s);   // move-construction from the local "s", never elided
}

The explicit cast makes s ineligible for RVO, so the return value will be move-constructed from s.