Commit 920ba938 authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Add missing include.

parent 118924c0
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Useful concepts and STL](/notebooks/5-UsefulConceptsAndSTL/0-main.ipynb) - [Move semantics](/notebooks/5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Motivation:-eliminate-unnecessary-deep-copies" data-toc-modified-id="Motivation:-eliminate-unnecessary-deep-copies-1">Motivation: eliminate unnecessary deep copies</a></span></li><li><span><a href="#A-traditional-answer:-to-allow-the-exchange-of-internal-data" data-toc-modified-id="A-traditional-answer:-to-allow-the-exchange-of-internal-data-2">A traditional answer: to allow the exchange of internal data</a></span></li><li><span><a href="#Reminder-on-references-in-C++03" data-toc-modified-id="Reminder-on-references-in-C++03-3">Reminder on references in C++03</a></span></li><li><span><a href="#C++11/14-:-temporary-references" data-toc-modified-id="C++11/14-:-temporary-references-4">C++11/14 : temporary references</a></span></li><li><span><a href="#Function-with-r-value-arguments" data-toc-modified-id="Function-with-r-value-arguments-5">Function with r-value arguments</a></span></li><li><span><a href="#std::move" data-toc-modified-id="std::move-6"><code>std::move</code></a></span></li><li><span><a href="#Move-constructors" data-toc-modified-id="Move-constructors-7">Move constructors</a></span></li><li><span><a href="#Temporary-reference-argument-within-a-function" data-toc-modified-id="Temporary-reference-argument-within-a-function-8">Temporary reference argument within a function</a></span></li><li><span><a href="#Move-semantics-in-the-STL" data-toc-modified-id="Move-semantics-in-the-STL-9">Move semantics in the STL</a></span></li><li><span><a href="#Forwarding-reference-(or-universal-reference)" data-toc-modified-id="Forwarding-reference-(or-universal-reference)-10">Forwarding reference (or universal reference)</a></span></li></ul></div>
%% Cell type:markdown id: tags:
## Motivation: eliminate unnecessary deep copies
In many situations, unnecessary deep copies are made.
In the example below, during the exchange between the two instances of the `Text` class, we have to make 3 memory deallocations, 3 allocations, 3 character copy loops... where 3 pointer copies would be sufficient.
%% Cell type:code id: tags:
``` C++17
#include <cstring>
#include <iostream>
class Text
{
public :
// For next section - don't bother yet
friend void swap(Text& lhs, Text& rhs);
Text(const char* string);
// Copy constructor.
Text(const Text& t);
// Recopy operator; defined here due to an issue of Xeus-cling with operators
Text& operator=(const Text& t)
{
std::cout << "Operator= called" << std::endl;
if (this == &t)
return *this ; // standard idiom to deal with auto-recopy
delete [] data_;
size_ = t.size_ ;
data_ = new char[t.size_] ;
std::copy(t.data_, t.data_ + size_, data_);
return *this ;
}
~Text();
// Overload of operator<<, defined here due to an issue of Xeus-cling with operators.
friend std::ostream & operator<<(std::ostream& stream, const Text& t)
{
return stream << t.data_ ;
}
private :
unsigned int size_{0};
char* data_ = nullptr;
} ;
```
%% Cell type:code id: tags:
``` C++17
Text::Text(const char* string)
{
std::cout << "Constructor called" << std::endl;
size_ = std::strlen(string) + 1;
data_ = new char[size_] ;
std::copy(string, string + size_, data_);
}
```
%% Cell type:code id: tags:
``` C++17
Text::Text(const Text& t)
: size_(t.size_), data_(new char [t.size_])
{
std::cout << "Copy constructor called" << std::endl;
std::copy(t.data_, t.data_ + size_, data_);
}
```
%% Cell type:code id: tags:
``` C++17
Text::~Text()
{
std::cout << "Destructor called" << std::endl;
delete[] data_;
}
```
%% Cell type:code id: tags:
``` C++17
{
Text t1("world!") ;
Text t2("Hello") ;
// Swap of values:
Text tmp = t1 ;
t1 = t2 ;
t2 = tmp ;
std::cout << t1 << " " << t2 << std::endl;
}
```
%% Cell type:markdown id: tags:
## A traditional answer: to allow the exchange of internal data
By allowing two texts to exchange (swap) their internal data, we can rewrite our program in a much more economical way in terms of execution time:
%% Cell type:code id: tags:
``` C++17
void swap(Text& lhs, Text& rhs)
{
unsigned int tmp_size = lhs.size_;
char* tmp_data = lhs.data_;
lhs.size_ = rhs.size_;
lhs.data_ = rhs.data_;
rhs.size_ = tmp_size;
rhs.data_ = tmp_data;
}
```
%% Cell type:code id: tags:
``` C++17
{
Text t1("world!") ;
Text t2("Hello") ;
// Swap of values:
swap(t1, t2);
std::cout << t1 << " " << t2 << std::endl;
}
```
%% Cell type:markdown id: tags:
There is even a `std::swap` in the STL that may be overloaded for your own types.
Now let's see how C++11 introduces new concepts to solve this (and many other) problems in a more elegant way.
## Reminder on references in C++03
C++ references allow you to attach a new name to an existing object in the stack or heap. All accesses and modifications made through the reference affect the original object:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
int var = 42;
int& ref = var; // Create a reference to var
ref = 99;
std::cout << "And now var is also 99: " << var << std::endl;
}
```
%% Cell type:markdown id: tags:
A reference can only be attached to a stable value (left value or **l-value**), which may broadly be summarized as a value which address may be taken (see \cite{Meyers2015} on this topic - its reading is especially interesting concerning this topic that is not always explained properly elsewhere - especially on the Web).
By opposition a **r-value** is a temporary value such as a literal expression or a temporary object created by implicit conversion.
%% Cell type:code id: tags:
``` C++17
{
int& i = 42 ; // Compilation error: 42 is a r-value!
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void print(std::string& lvalue)
{
std::cout << "l-value is " << lvalue << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
print("hello") ; // Compilation error: "hello" is a r-value!
}
```
%% Cell type:markdown id: tags:
Look carefully at the error message: the issue is not between `const char[6]` and `std::string` (implicit conversion from `char*` to `std::string` exists) but due to the reference; same function with pass-by-copy works seemlessly:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <string>
void print_by_copy(std::string value) // no reference here!
{
std::cout << "l- or r- value is " << value << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
print_by_copy("hello") ; // Ok!
}
```
%% Cell type:markdown id: tags:
Noteworthy exception: a reference "constant" (language abuse designating a reference to a constant value) can be attached to a temporary value, in particular to facilitate implicit conversions:
%% Cell type:code id: tags:
``` C++17
void print_by_const_ref(const std::string& lvalue)
{
std::cout << "l-value is " << lvalue << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
print_by_const_ref("hello") ; // Ok!
}
```
%% Cell type:markdown id: tags:
## C++11/14 : temporary references
To go further, C++11 introduces the concept of **r-value reference**, which can only refer to temporary values, and is declared using an `&&`.
%% Cell type:code id: tags:
``` C++17
{
int&& i = 42;
}
```
%% Cell type:code id: tags:
``` C++17
{
int j = 42;
int&& k = j; // Won’t compile: j is a l-value!
}
```
%% Cell type:markdown id: tags:
It is now possible to overload a function to differentiate the treatment to be performed according to whether it is provided with a stable value or a temporary value. Below, function `f` is provided in three variants:
````
void f(T&); // I : argument must be a l-value
void f(const T&) ; // II : argument may be l-value or r-value but can't be modified
void f(T&&); // III : argument must be a r-value
````
%% Cell type:markdown id: tags:
In case of a call of `f` with a temporary value, it is now form III that will be invoked, if it is defined. This is the cornerstone of the notion of **move semantic**.
%% Cell type:markdown id: tags:
## Function with r-value arguments
When we know that a value is temporary, we must be able to use it again, or "loot" its content without harmful consequences; _move_ it instead of _copying_ it. When handling large dynamic data structures, it can save many costly operations.
Let's take a function that receives a vector of integers and replicates it to modify it. The old way would be as follows:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <vector>
void print_double(const std::vector<int>& vec)
{
std::cout << "print_double for l-value" << std::endl;
std::vector<int> copy(vec);
for (auto& item : copy)
item *= 2;
for (auto item : copy)
std::cout << item << "\t";
std::cout << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 };
print_double(primes);
}
```
%% Cell type:markdown id: tags:
If the original object is temporary, copying it is not necessary. This can be exploited through this overload of the function:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void print_double(std::vector<int>&& vec)
{
std::cout << "print_double for r-value" << std::endl;
for (auto& item : vec)
item *= 2;
for (auto item : vec)
std::cout << item << "\t";
std::cout << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
print_double(std::vector<int>{ 2, 3, 5, 7, 11, 13, 17, 19 });
}
```
%% Cell type:markdown id: tags:
## `std::move`
Now, if we get a l-value and know we do not need it anymore in the current scope, we may choose to cast is as a r-value through a **static_cast**:
%% Cell type:code id: tags:
``` C++17
{
std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 };
print_double(static_cast<std::vector<int>&&>(primes));
}
```
%% Cell type:markdown id: tags:
And we see overload call is properly the one for r-values.
The syntax is a bit heavy to type, so a shorter one was introduced as well: **`std::move`**:
%% Cell type:code id: tags:
``` C++17
{
std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 };
print_double(std::move(primes)); // strictly equivalent to the static_cast in former cell!
}
```
%% Cell type:markdown id: tags:
Please notice that the call to `std::move` does not move `primes` per se. It only makes it a temporary value in the eyes of the compiler, so it is a "possibly" movable object if the context allows it; if for instance the object doesn't define a move constructor (see next section), no move will occur!
%% Cell type:markdown id: tags:
## Move constructors
In classes, C++ introduced with move semantics two additional elements in the canonical form of the class:
- A **move constructor**
- A **move assignment operator**
%% Cell type:code id: tags:
``` C++17
#include <cstring>
#include <iostream>
class Text2
{
public :
friend void swap(Text2& lhs, Text2& rhs);
Text2(const char* string);
// Copy constructor.
Text2(const Text2& t);
// Move constructor
Text2(Text2&& t);
// Recopy operator; defined here due to an issue of Xeus-cling with operators
Text2& operator=(const Text2& t)
{
std::cout << "Operator= called" << std::endl;
if (this == &t)
return *this ; // standard idiom to deal with auto-recopy
delete [] data_;
size_ = t.size_ ;
data_ = new char[t.size_] ;
std::copy(t.data_, t.data_ + size_, data_);
return *this ;
}
// Move assignment operator; defined here due to an issue of Xeus-cling with operators
Text2& operator=(Text2&& t)
{
std::cout << "Operator= called for r-value" << std::endl;
if (this == &t)
return *this;
delete[] data_;
size_ = t.size_;
data_ = t.data_;
// Don't forget to properly invalidate `t` content:
t.size_ = 0 ;
t.data_ = nullptr ;
return *this ;
}
~Text2();
// Overload of operator<<, defined here due to an issue of Xeus-cling with operators.
friend std::ostream & operator<<(std::ostream& stream, const Text2& t)
{
return stream << t.data_ ;
}
private :
unsigned int size_{0};
char* data_ = nullptr;
} ;
```
%% Cell type:code id: tags:
``` C++17
Text2::Text2(const char* string)
{
std::cout << "Constructor called" << std::endl;
size_ = std::strlen(string) + 1;
data_ = new char[size_] ;
std::copy(string, string + size_, data_);
}
```
%% Cell type:code id: tags:
``` C++17
Text2::Text2(const Text2& t)
: size_(t.size_), data_(new char [t.size_])
{
std::cout << "Copy constructor called" << std::endl;
std::copy(t.data_, t.data_ + size_, data_);
}
```
%% Cell type:code id: tags:
``` C++17
Text2::Text2(Text2&& t)
: size_(t.size_), data_(t.data_)
{
std::cout << "Move constructor called" << std::endl;
t.size_ = 0 ;
t.data_ = nullptr ;
}
```
%% Cell type:code id: tags:
``` C++17
Text2::~Text2()
{
std::cout << "Destructor called" << std::endl;
delete[] data_;
}
```
%% Cell type:code id: tags:
``` C++17
{
Text2 t1("world!") ;
Text2 t2("Hello") ;
// Swap of values:
Text2 tmp = std::move(t1);
t1 = std::move(t2);
t2 = std::move(tmp);
std::cout << t1 << " " << t2 << std::endl;
}
```
%% Cell type:markdown id: tags:
With all this move semantics, the operations above are comparable to what we achieved with the `swap` function for `Text` earlier... with the additional benefit that this semantic is not only used for swapping two values.
As already mentioned [there](/notebooks/3-Operators/4-CanonicalForm.ipynb#[Advanced]-The-true-canonical-class), there are specific rules called __Rule of 0__, __Rule of 3__ and __Rule of 5__, which explains which constructor(s), destructor and assigmnent operator you ought to define for your class.
## Temporary reference argument within a function
A crucial point now: if a function receives a temporary reference argument (which can only be attached to a temporary value), within the function this argument is considered as l-value (we can perfectly put it to the left of an = and reassign a new value). If the function does not itself loot the content of the variable, and transmits it to another function (or constructor or operator), it can only reactivate its temporary character using a call to `std::move`.
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <string>
void do_stuff(std::string&& string)
{
std::cout << "Argument given by r-value is: " << string << std::endl;
string = "Bye!";
std::cout << "It was nonetheless modified as it is **inside the function** a l-value: " << string << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
do_stuff("Hello!");
}
```
%% Cell type:markdown id: tags:
## Move semantics in the STL
All containers in the standard library are now enhanced with move constructors and move assignment operators.
Moreover, the move semantics is not only about improving performance. There are classes (such as `std::unique_ptr` we'll see in [next notebook](/notebooks/5-UsefulConceptsAndSTL/6-SmartPointers.ipynb#unique_ptr)) for which it makes no sense for objects to be copyable, but where it is necessary for them to be movable. In this case, the class has a constructor per move, and no constructor per copy.
An object that has been "emptied" as a result of a move is no longer supposed to be useful for anything. However if not destroyed it is still recommended, when you implement this type of class and move, to leave the emptied object in a "valid" state; that's why we put in our `Text2` class the `data_` pointer to `nullptr` and the `size_` to 0. The best is of course to ensure its destruction at short notice to avoid any mishap.
%% Cell type:markdown id: tags:
## Forwarding reference (or universal reference)
Just a quick warning (you should really read \cite{Meyers2015} for an extensive discussion on the topic; blog [FluentCpp](https://www.fluentcpp.com/2018/02/06/understanding-lvalues-rvalues-and-their-references) provides some intel about it... and tells you as well to ready Scott Meyer's book to learn more!): seeing `&&` doesn't automatically mean it is a r-value reference.
There is a very specific case when:
- The argument is template
- The parameter is **exactly** `T&&` (not `std::vector<T&&>` for instance)
in which the syntax stands for either case (l-value or r-value)
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <string>
template<class T>
void PrintUniversalRef(T&& value)
{
std::cout << value << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
PrintUniversalRef("r-value call!"); // will call a specialisation of the template for r-value
std::string hello("l-value call!"); // will call a specialisation of the template for l-value
PrintUniversalRef(hello);
}
```
%% Cell type:markdown id: tags:
Unfortunately, C++ 11 committee didn't give a name to this specific call; Scott Meyers first publicized it under the name **universal reference**... and was not followed by the C++ committee that finally chose **forwarding reference**. You may therefore find one or the other term, but the idea behind is exactly the same.
%% Cell type:markdown id: tags:
# References
[<a id="cit-Meyers2015" href="#call-Meyers2015">Meyers2015</a>] Scott Meyers, ``_Effective modern C++: 42 specific ways to improve your use of C++11
and C++14_'', 2015. [online](http://www.worldcat.org/oclc/890021237)
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_
_This notebook is an adaptation of a lecture prepared and redacted by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been redacted by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment