Move Semantic: Two nice Properties

Contents[Show]

I will talk about two nice properties of the move semantic in this post that are not so often mentioned. Containers of the standard template library (STL) can have non-copyable elements. The copy semantic is the fallback for the move semantic. Irritated? I hope so!

 

Moving instead of copying

Do you remember the program packagedTask.cpp from the post Asynchronous callable wrappers? Of course, not. Here, once more.

Moving elements in a container

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// packagedTask.cpp

#include <utility>
#include <future>
#include <iostream>
#include <thread>
#include <deque>

class SumUp{
  public:
    int operator()(int beg, int end){
      long long int sum{0};
      for (int i= beg; i < end; ++i ) sum += i;
      return sum;
    }
};

int main(){

  std::cout << std::endl;

  SumUp sumUp1;
  SumUp sumUp2;
  SumUp sumUp3;
  SumUp sumUp4;

  // define the tasks
  std::packaged_task<int(int,int)> sumTask1(sumUp1);
  std::packaged_task<int(int,int)> sumTask2(sumUp2);
  std::packaged_task<int(int,int)> sumTask3(sumUp3);
  std::packaged_task<int(int,int)> sumTask4(sumUp4);

  // get the futures
  std::future<int> sumResult1= sumTask1.get_future();
  std::future<int> sumResult2= sumTask2.get_future();
  std::future<int> sumResult3= sumTask3.get_future();
  std::future<int> sumResult4= sumTask4.get_future();

  // push the tasks on the container
  std::deque< std::packaged_task<int(int,int)> > allTasks;
  allTasks.push_back(std::move(sumTask1));
  allTasks.push_back(std::move(sumTask2));
  allTasks.push_back(std::move(sumTask3));
  allTasks.push_back(std::move(sumTask4));
  
  int begin{1};
  int increment{2500};
  int end= begin + increment;

  // execute each task in a separate thread
  while ( not allTasks.empty() ){
    std::packaged_task<int(int,int)> myTask= std::move(allTasks.front());
    allTasks.pop_front();
    std::thread sumThread(std::move(myTask),begin,end);
    begin= end;
    end += increment;
    sumThread.detach();
  }

  // get the results
  auto sum= sumResult1.get() + sumResult2.get() + sumResult3.get() + sumResult4.get();

  std::cout << "sum of 0 .. 10000 = " << sum << std::endl;

  std::cout << std::endl;

}

 

I'm not interested in the fact that the program calculates the sum of the numbers from 0 .. 10000 in four threads.

I'm interested in a completely different property of std:::packaged_task. std::packaged_task is not copyable. The reason is simple. The copy constructor and copy assignment operator are set to delete. You can read the details here: cppreference.com.

How is it possible to use std::package_task as element in a container of the STL? Containers of the STL wants to own their elements. Therefore, I move via std::move the std::package_task objects (line 41 - 44) into the container std::deque. Consequently, I have to use move semantic in line 52 and 54 because I can not copy the std::package_task.

But that is not the end of the story. If an algorithm of the STL uses under the hood no copy semantic, you can apply it to containers with non-copyable elements. If the algorithm uses internally copy semantic, you will get a compiler error.

Algorithm on only moveable elements 

In the next example, I make it very simple. I define a simple wrapper MyInt for natural numbers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// moveAlgorithm.cpp
#include <numeric> 
#include <algorithm> 
#include <iostream>
#include <utility>
#include <vector>
 
class MyInt{
public:
    MyInt(int i_):i(i_){}
    
    // copy semantic
    MyInt(const MyInt&)= delete;
    MyInt& operator= (const MyInt&)= delete;
    
    // move semantic
    MyInt(MyInt&&)= default;
    MyInt& operator= (MyInt&&)= default;
    
    int getVal() const {
        return i;
    }
private:
    int i;
};

int main(){
    
    std::cout << std::endl;
    
    std::vector<MyInt> vecMyInt;
    for (auto i= 1; i <= 10; ++i){
        vecMyInt.push_back(std::move(MyInt(i)));
    }
    
    std::for_each(vecMyInt.begin(), vecMyInt.end(), [](MyInt& myInt){ std::cout << myInt.getVal() << " "; });
    
    std::cout << std::endl;
    
    auto myInt= MyInt(std::accumulate(vecMyInt.begin(), vecMyInt.end(),MyInt(1),[](MyInt& f, MyInt& s){ return f.getVal() * s.getVal(); }));
    
    std::cout << "myInt.getVal(): " << myInt.getVal() << std::endl;
    
    std::cout << std::endl;
    
}

 

I only declaratively use delete (line 13 and 14) for the copy semantic and default (line 17 and 18) for the move semantic. Therefore, the compiler will do the right job for me. I implement only the constructor and the getter getVal (line 20 - 22). Although my type is non-copyable I use it in a std::for_each (line 36) and std::accumulate (line 40) algorithm of the STL.

There are not big surprises.

moveAlgorithm

The program will not compile with cl.exe

Unfortunately, the program will not compile with a recent cl.exe Compiler (19.10.24807.0 (x86)). Microsoft std::accumulate versions uses under the hood copy semantic. This is in opposite to recent GCC or clang compilers, which uses move semantic. But cl.exe is right. std::accumulate  requires that T must be copy assignable and copy constructable. This will not hold for MyInt. This issue is still under discussion: https://gcc.gnu.org/onlinedocs/libstdc%2B%2B/ext/lwg-active.html

 

The copy semantic is a fallback for the move semantic. What does that mean?

Copy semantic as fallback for the move semantic

If I write an algorithm that internally uses move semantic, I can apply that algorithm on non-copyable types. I have only to change my type MyInt.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// copyFallbackMove.cpp

#include <iostream>
#include <type_traits>
#include <utility>

template <typename T>
void swapMove(T& a, T& b){
    T tmp(std::move(a));
    a= std::move(b);
    b= std::move(tmp);
}

class MyInt{
public:
    MyInt(int i_):i(i_){}
    
    // copy semantic
    MyInt(const MyInt& myInt):i(myInt.getVal()){}
    MyInt& operator= (const MyInt& myInt){
        i= myInt.getVal();
        return *this;
    }
        
    int getVal() const {
        return i;
    }
private:
    int i;
};

int main(){
    
    std::cout << std::endl;
    MyInt myInt1(1);
    MyInt myInt2(2);
    
    std::cout << std::boolalpha;
    
    std::cout << "std::is_trivially_move_constructible<MyInt>::value " << std::is_trivially_move_constructible<MyInt>::value << std::endl;
    std::cout << "std::is_trivially_move_assignable<MyInt>::value " << std::is_trivially_move_assignable<MyInt>::value << std::endl;
    
    std::cout << "myInt1.getVal() :" << myInt1.getVal() << std::endl;
    std::cout << "myInt2.getVal() :" << myInt2.getVal() << std::endl;
    
    swapMove(myInt1,myInt2);
    std::cout << std::endl;
    
    std::cout << "myInt1.getVal() :" << myInt1.getVal() << std::endl;
    std::cout << "myInt2.getVal() :" << myInt2.getVal() << std::endl;
    
    std::cout << std::endl;
    
}

 

MyInt has a user-defined copy constructor and copy assignment operator. Therefore, the compiler will not automatically generate a move constructor or move assignment operator. You can read the details here (move semantic) or can ask the type traits library (line 40 and 41) for help. Sadly, my GCC don't support this function of the type traits library. So I have to use a more recent GCC 5.2. The key point is that instances of MyInt can be used in the function template swapMove (line 7 - 12) although these instances don't support move semantic. 

 

copyFallbackMove

 

The reason is. A rvalue can be bound to

  • a constant lvalue reference.
  • a non-constant rvalue reference.

The non-constant rvalue reference has higher priority than the constant lvalue reference. A copy constructor or a copy assignment operator expect its arguments as constant lvalue reference. A move constructor or a move assignment operator expect its arguments as non-constant rvalue reference. What sound a little bit confusing has a very nice property. 

Keep the performance in your mind

You can implement functions like swapMove with move semantic in mind. If your data types are not moveable the functions will also work for only copyable types. The compiler will use the classic copy semantic as fallback. Therefore, you can quite comfortable migrate an old C++ code base to modern C++. Your program is in the first iteration correct and in the second iteration fast.

 

What's next?

Writing function temples that can identically forward their arguments, was a " ... a heretofore unsolved problem in C++.". (Bjarne Stroustrup). Was, because since C++11 std::forward we can use perfect forwarding. Read the details in the next post.


 

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 

Tags: memory

Comments   

0 #1 Andrey 2016-12-30 00:50
Hi! My question is about moveAlgorithm.cpp file.
I tryed to build it using Visual Studio 2015 compiler. I have to #include for std::accumulate. But my problem is in Line 40.
The error C2280: 'MyInt::MyInt(const MyInt &)': attempting to reference a deleted function...
Quote
0 #2 Rainer Grimm 2016-12-30 07:45
Quoting Andrey:
Hi! My question is about moveAlgorithm.cpp file.
I tryed to build it using Visual Studio 2015 compiler. I have to #include for std::accumulate. But my problem is in Line 40.
The error C2280: 'MyInt::MyInt(const MyInt &)': attempting to reference a deleted function...

Good point. There is an issue with line 40. I will add a few words to the post.
Quote
0 #3 Towels 2017-03-09 12:49
Good article! We are linking to this particularly great post on our website.
Keep up the great writing.
Quote

Add comment


My Newest E-Books

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 610

All 382632

Currently are 176 guests and no members online