Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a simple example of move construction that won't get elided?

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?

like image 761
user1628444 Avatar asked Sep 05 '12 16:09

user1628444


People also ask

Is move constructor shallow copy?

Shallow copy - yes.

What is the difference between move constructor and copy constructor?

If any constructor is being called, it means a new object is being created in memory. So, the only difference between a copy constructor and a move constructor is whether the source object that is passed to the constructor will have its member fields copied or moved into the new object.

What is a move constructor?

A move constructor allows the resources owned by an rvalue object to be moved into an lvalue without creating its copy. An rvalue is an expression that does not have any memory address, and an lvalue is an expression with a memory address.

Is copy elision guaranteed?

You can see this behavior in compiler versions Visual Studio 2017 15.6, Clang 4, GCC 7, and above. Despite the name of the paper and what you might read on the Internet, the new rules do not guarantee copy elision. Instead, the new value category rules are defined such that no copy exists in the first place.


2 Answers

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.

like image 63
David Rodríguez - dribeas Avatar answered Oct 12 '22 13:10

David Rodríguez - dribeas


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.

like image 33
Kerrek SB Avatar answered Oct 12 '22 12:10

Kerrek SB