Move semantics
En C++, les objets sont copiés par défaut. Et c'est très bien car ça é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 l'élément dans V
Parfois copier c'est débile
V.push_back(getStringFromClient())
getStringFromClient()
renvoie un objetstring
que l'on note A (il n'y a pas de variable A dans le programme)- On crée une copie A' que l'on donne à
push_back
- Puis A est supprimé
C'est dommage de copier A pour le supprimer juste après.
Idée générale de la move semantics
Depuis C++11, il y a la move semantics. En reprenant l'exemple précédent, voici comment cela fonctionne :
getStringFromClient()
renvoie un objetstring
que l'on note A- On crée A' qui reçoit les données de A
- On met A dans un état de sorte de coquille vide
- On donne A' à
push_back
- A est supprimé
std::move
Voici des situations où C++ fait une copie alors qu'un move aurait été plus efficace, mais le compilateur ne peut pas le deviner.
Exemples
-
Situation où on a un nom car on utilise l'objet plusieurs fois :
{ string s("Bloup"); V.push_back(s); V.push_back(s); // là on aurait du faire move semantics car s est de toute façon détruit après }
-
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
, que l'on note ici move
:
{
string s("Bloup");
V.push_back(s);
V.push_back(move(s)); // :)
}
et
void reinit(string& s) {
history.push_back(move(s)); // :)
s = getDefaultValue();
}
Recap
move(s)
signifie :
-
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(move(a)); a = move(b); b = move(tmp); }
En fait, move(s)
c'est équivalent à static_cast<string&&>(y)
.
Implémentation côté vector
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(getName())
) or it is explicitely a moved object (e.g.V.push_back(move(s))
).
Implémentation côté string
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