Qu'est-ce que l'idiome Copy-and-Swap et pourquoi est-ce un must-have en 2025
Imaginez que vous écrivez une classe pour gérer la mémoire dynamique. Tout va bien, jusqu'à ce que vous arriviez à la surcharge de l'opérateur d'affectation. Vous écrivez operator=, vous vérifiez l'auto-affectation, vous libérez manuellement les anciennes ressources, vous en allouez de nouvelles… Et dans 90 % des cas, vous introduisez un bug qui entraînera une fuite mémoire ou une double suppression. Cela vous dit quelque chose ?
En C++, il existe une solution élégante — l'idiome copy-and-swap. Ce n'est pas simplement une astuce à la mode, mais une technique fondamentale qui permet d'écrire du code sûr, résistant aux exceptions et concis. En 2025, alors que le C++23 est déjà activement utilisé et que la norme C++26 se profile à l'horizon, cet idiome reste l'étalon-or pour les classes qui gèrent des ressources.
Dans cet article, nous allons décortiquer l'idiome copy-and-swap de A à Z : en commençant par le problème qu'il résout, jusqu'aux exemples pratiques et aux meilleures pratiques de 2025. Préparez votre compilateur — il y aura beaucoup de code !
Le problème : pourquoi un operator= naïf est un mal
Commençons par l'exemple classique d'une classe qui gère un tableau dynamique. Sans l'idiome copy-and-swap, le code ressemble souvent à ceci :
class DumbArray {public: DumbArray(size_t size) : size_(size), data_(new int[size]) {} ~DumbArray() { delete[] data_; } // Opérateur d'affectation naïf DumbArray& operator=(const DumbArray& other) { if (this == &other) return *this; // vérification d'auto-affectation delete[] data_; // libération des anciennes ressources size_ = other.size_; data_ = new int[size_]; // allocation de nouvelles std::copy(other.data_, other.data_ + size_, data_); return *this; } private: size_t size_; int* data_;};À première vue, tout semble logique. Mais il y a trois problèmes :
- Absence de garantie de base contre les exceptions. Si
new int[size_]lèvestd::bad_alloc, l'objet reste dans un état incorrect :data_pointera vers une mémoire supprimée. - Duplication de la logique. La copie des données est déjà implémentée dans le constructeur de copie. Pourquoi écrivons-nous la même chose manuellement ?
- Vérification d'auto-affectation. Elle ralentit le code et est facilement oubliée. De plus, dans 95 % des cas, l'auto-affectation ne se produit pas, mais la vérification est quand même effectuée.
Et ce n'est pas tout. Si vous ajoutez d'autres ressources à la classe (par exemple, des descripteurs de fichiers ou des mutex), le code deviendra non seulement volumineux, mais aussi dangereux. C'est là que l'idiome copy-and-swap entre en scène.
L'essence de l'idiome : trois étapes vers un operator= idéal
L'idiome copy-and-swap repose sur une idée simple : n'écrivez pas la logique de copie dans l'opérateur d'affectation — utilisez le constructeur de copie et la fonction swap. L'algorithme est le suivant :
- Copy : créez une copie temporaire de l'objet à l'aide du constructeur de copie.
- Swap : échangez le contenu de l'objet courant avec la copie temporaire.
- Destroy : l'objet temporaire est détruit, libérant les anciennes ressources.
En code, cela donne :
class SmartArray {public: SmartArray(size_t size = 0) : size_(size), data_(size ? new int[size] : nullptr) {} // Constructeur de copie SmartArray(const SmartArray& other) : size_(other.size_), data_(other.size_ ? new int[other.size_] : nullptr) { std::copy(other.data_, other.data_ + size_, data_); } // Destructeur ~SmartArray() { delete[] data_; } // Fonction swap amie friend void swap(SmartArray& first, SmartArray& second) noexcept { using std::swap; swap(first.size_, second.siz