Move Semantis: Two Nice Properties

Contents[Show]

I will talk about two nice properties of the move semantic in this post that is 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 an element in a container of the STL? Containers of the STL want 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 the 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 semantics, you will get a compiler error.

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Stay informed about my mentoring programs.

 

 

Subscribe via E-Mail.

The 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 no 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 use under the hood copy semantics. This is opposite to recent GCC or clang compilers, which uses move semantic. But cl.exe is right. std::accumulate  requires that T must be copied assignable and copy constructible. 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 Semantics as a Fallback for Move Semantics

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 (lines 40 and 41) for help. Sadly, my GCC doesn'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 a higher priority than the constant lvalue reference. A copy constructor or a copy assignment operator expects its arguments as a constant lvalue reference. A move constructor or a move assignment operator expects its arguments as a non-constant rvalue reference. What sounds 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 a fallback. Therefore, you can quite comfortably migrate an old C++ codebase 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). This was because since C++11 std::forward we can use perfect forwarding. Read the details in the next post.


 

 

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, and Ann Shatoff.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, and Slavko Radman.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

My special thanks to Tipi.build tipi.build logo

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

  • C++ - The Core Language
  • C++ - The Standard Library
  • C++ - Compact
  • C++11 and C++14
  • Concurrency with Modern C++
  • Design Pattern and Architectural Pattern with C++
  • Embedded Programming with Modern C++
  • Generic Programming (Templates) with C++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

 

Tags: Memory, move

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

Mentoring

Stay Informed about my Mentoring

 

English Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

All tags

Blog archive

Source Code

Visitors

Today 695

Yesterday 7888

Week 8583

Month 152754

All 11633908

Currently are 198 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments