Move semantics
En C++, les objets sont copiés par défaut. C'est inspiré de C. Ca évite les effets de bords.
string s("Bonjour");
vector<string> V;
V.push_back(s);
... on continue à utiliser
s[1] = "c"; // s est modifé mais pas la copie de s dans V
Parfois copier c'est débile
Supposons que l'on a un tableau de trucs.
class MyVector {
string* data;
size_t size;
size_t capacity;
public:
vector(){
size = 0;
capacity = 2;
data = new truc[capacity];
}
resize(size_t new_capacity) {
truc* new_data = new truc[new_capacity];
for(int i = 0; i < size; i++) {
new_data[i] = data[i];
}
std::swap(data, new_data);
delete[] new_data;
capacity = new_capacity;
}
void push_back(const truc& s){
if(size == capacity) {
resize(capacity * 2);
}
data[size] = s;
size++;
}
}
Le problème est dans
```cpp
new_data[i] = data[i];
```
Ca fait une copie des trucs ! alors que data
va disparaître juste après.
Idée
Ce qu'on fait avec la copie :
TODO image avec des fruits
Idée pour améliorer :
Idée générale de la move semantics
Depuis C++11 (hé oui !), il y a la move semantics. Un move c'est une copie superficielle + on rend l'ancien élément "vide". En reprenant l'exemple précédent, voici comment cela fonctionne :
- on met les données dans
new_data[i]
data[i]
est maintenant dans un état de sorte de coquille vide
Le compilateur ne pouvait pas deviner que data[i]
allait être perdu. Là, on l'a rendu "coquille vide" donc ce n'est pas grave.
Exemples
Voici des situations où C++ fait une copie alors qu'un move aurait été plus efficace, mais le compilateur ne peut pas le deviner.
- Situation où on a un nom car on utilise l'objet plusieurs fois :
{
string s("Bloup");
V.push_back(s); // là on aurait du faire move semantics car s est de toute façon détruit après le }
}
- Situation où on a un paramètre
void reinit(string& s) {
history.push_back(s); // là on aimerait avoir move semantics car s n'est plus utilisé après
s = getDefaultValue();
}
Solution
La solution est d'utiliser std::move
:
{
string s("Bloup");
V.push_back(std::move(s)); // :)
}
et
void reinit(string& s) {
history.push_back(std::move(s)); // :)
s = getDefaultValue();
}
Recap
std::move(s)
signifie intuitivement :
s
n'est plus utile ici- tu me déplaces au lieu de me copier
- après l'appel
s
est toujours un objet valide mais sa valeur est quelconque (souvent vide a priori). On peut réutiliser la variables
.
void swap(string& a, string& b) {
string tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
Comment ça marche... que fait std::move
?
En C++, on distingue :
- les
lvalue
: ce sont des noms de variables, ou des cellules mémoires adressées par un pointeur, etc. (l
pour location, ou pour left) - les
rvalue
qui sont des valeurs non nommés comme5
,f(3)
(r
pour right)
On va s'appuyer sur cette distinction. En effet, les lvalue
sont amenés à rester en mémoire, alors que les rvalue
sont amenés à mourir vite.
En C++, on note :
string&
une référence sur unelvalue
string&&
une référence sur unervalue
(c'est unelvalue
qui est une référence sur unervalue
)
L'appel std::move(s)
renvoie s
mais en tant que rvalue
pour dire qu'elle va mourir et donc qu'il faut faire une move semantics. En fait, std::move(s)
c'est équivalent à static_cast<string&&>(y)
. Maintenant y
, tu vas mourir !
Move semantics sur une méthode quelconque
template <typename T>
class vector {
public:
//copy elem into the vector
void push_back(const T& elem);
//**move** elem into the vector
void push_back(T&& elem);
}
void push_back(const T& elem)
is called in old version of C++ ;) or in C++11 when there is a variable name in the call (e.g.V.push_back(s)
)- In C++11,
void push_back(T&& elem)
is called when there is no name (e.g.V.push_back(2)
,V.push_back(getName())
) or it is explicitely a moved object (e.g.V.push_back(move(s))
).
Move semantics sur un constructeur
class string {
private:
int len;
char* data;
public:
// copy constructor
string(const string& a) : len(s.len) {
data = new char(len+1);
memcpy(data, s.data, len+1);
}
//move constructor
string(string&& s) : len(s.len), data(s.data) {
s.data = nullptr;
s.len = 0;
}
}
Pour aller plus loin
- https://www.thecodedmessage.com/posts/cpp-move/
- Vidéo à CPPCON 2021 sur la move semantics, dont est inspiré ce cours https://www.youtube.com/watch?v=Bt3zcJZIalk