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 program that 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 (lines 41 - 44) into the container std::deque. Consequently, I must use the move semantic in lines 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 semantics, you can apply it to containers with non-copyable elements. You will get a compiler error if the algorithm uses internal copy semantics.

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Be part of my mentoring programs:

 

 

 

 

Do you want to 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 (lines 13 and 14) for the copy semantic and default (lines 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 (lines 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 use move semantics. 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 semantics, I can apply that algorithm to 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 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 critical point is that instances of MyInt can be used in the function template swapMove (lines 7 - 12), although these instances don't support move semantics. 

 

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 lovely property. 

Keep the performance in your mind

You can implement functions like swapMove with move semantics 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 comfortably migrate an old C++ codebase to modern C++. Your program is correct in the first iteration and fast in the second.

 

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, Ann Shatoff, and Rob North.

 

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

 

My special thanks to Take Up Code TakeUpCode 450 60

 

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

Stay Informed about my Mentoring

 

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

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

Course: Master Software Design Patterns and Architecture in C++

Subscribe to the newsletter (+ pdf bundle)

All tags

Blog archive

Source Code

Visitors

Today 1762

Yesterday 4344

Week 38640

Month 18886

All 12097095

Currently are 140 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments