Middle+
Qu'est-ce que la sémantique du déplacement et comment l'utiliser?
sobes.tech IA
Réponse de l'IA
La sémantique de déplacement (move semantics) en C++11 et versions supérieures permet de transférer efficacement la propriété d'une ressource d'un objet à un autre, évitant ainsi les copies inutiles. Cela est réalisé en utilisant les références rvalue (&&) et l'opérateur de déplacement (std::move).
Aspects clés:
- Références rvalue (
&&): Références à des objets temporaires (c'est-à-dire, rvalues) ou à des objets marqués comme déplaçables. Permettent de se lier à un objet qui sera bientôt détruit. - Opérateur de déplacement (
std::move): Convertit un lvalue en une référence rvalue. Ne réalise pas le déplacement lui-même, mais indique au compilateur que l'objet peut être déplacé en toute sécurité. - Constructeur de déplacement: Constructeur spécifique qui accepte une référence rvalue en argument. "Vole" les ressources (par exemple, les pointeurs vers la mémoire dynamique) de l'objet source, le laissant dans un état valide mais vidé.
- Opérateur d'assignation par déplacement: Semblable au constructeur de déplacement, destiné aux opérations d'assignation.
Applications:
- Optimisation: Réduit les coûts liés à la copie de grands objets ou d'objets possédant des ressources (par exemple, conteneurs, chaînes, pointeurs intelligents).
- Implémentation de conteneurs efficaces: Permet aux conteneurs comme
std::vectorde gérer efficacement les éléments lors du changement de taille ou du déplacement. - Retourner de grands objets par valeur: Le compilateur peut appliquer automatiquement l'optimisation du déplacement (return value optimization, RVO, ou Named Return Values Optimization, NRVO), si l'objet retourné est une variable locale ou un objet temporaire.
Exemple:
#include <iostream>
#include <vector>
#include <string>
#include <utility> // Pour std::move
class MyResource {
public:
std::vector<int> data;
// Constructeur
MyResource(size_t size) : data(size) {
std::cout << "Constructeur" << std::endl;
}
// Destructeur
~MyResource() {
std::cout << "Destructeur" << std::endl;
}
// Constructeur de copie
MyResource(const MyResource& other) : data(other.data) {
std::cout << "Constructeur de copie" << std::endl;
}
// Opérateur d'assignation de copie
MyResource& operator=(const MyResource& other) {
if (this != &other) {
data = other.data;
}
std::cout << "Opérateur d'assignation de copie" << std::endl;
return *this;
}
// Constructeur de déplacement
MyResource(MyResource&& other) noexcept : data(std::move(other.data)) {
std::cout << "Constructeur de déplacement" << std::endl;
// Après déplacement, l'objet original est dans un état valide mais vidé
// other.data reste valide, mais son contenu peut avoir été déplacé
}
// Opérateur d'assignation par déplacement
MyResource& operator=(MyResource&& other) noexcept {
if (this != &other) {
data = std::move(other.data);
}
std::cout << "Opérateur d'assignation par déplacement" << std::endl;
return *this;
}
};
int main() {
MyResource res1(10); // Constructeur
std::cout << "---" << std::endl;
MyResource res2 = res1; // Constructeur de copie (ou opérateur d'assignation de copie si res2 existe déjà)
std::cout << "---" << std::endl;
MyResource res3 = std::move(res1); // Constructeur de déplacement
std::cout << "---" << std::endl;
MyResource res4(5); // Constructeur
res4 = std::move(res3); // Opérateur d'assignation par déplacement
std::cout << "---" << std::endl;
return 0;
}
Dans cet exemple, on voit que l'utilisation de std::move appelle le constructeur de déplacement ou l'opérateur d'assignation par déplacement au lieu de leurs homologues de copie, évitant ainsi des copies coûteuses du contenu du vecteur data.