web-dev-qa-db-fra.com

Comment la bibliothèque standard met-elle en œuvre std :: swap?

Comment la fonction de swap est-elle implémentée dans la STL? Est-ce aussi simple que cela:

template<typename T> void swap(T& t1, T& t2) {
    T tmp(t1);
    t1=t2;
    t2=tmp;
}

Dans d'autres articles, ils parlent de spécialiser cette fonction pour votre propre classe. Pourquoi aurais-je besoin de faire ça? Pourquoi ne puis-je pas utiliser le std::swap une fonction?

56
Maximilian Mordig

Comment est std::swap mis en œuvre?

Oui, l'implémentation présentée dans la question est celle classique en C++ 03.

Une implémentation plus moderne (C++ 11) de std::swap ressemble à ça:

template<typename T> void swap(T& t1, T& t2) {
    T temp = std::move(t1); // or T temp(std::move(t1));
    t1 = std::move(t2);
    t2 = std::move(temp);
}

Il s'agit d'une amélioration par rapport à l'implémentation C++ 03 classique en termes de gestion des ressources, car elle empêche les copies inutiles, etc. Il, le C++ 11 std::swap , nécessite que le type T soit MoveConstructible et MoveAssignable , permettant ainsi l'implémentation et les améliorations.

Pourquoi aurais-je besoin de fournir une implémentation personnalisée?

Une implémentation personnalisée de swap, pour un type spécifique, est généralement conseillée lorsque votre implémentation est plus efficace ou spécifique que la version standard.

Un exemple classique de ceci est lorsque votre classe gère une grande quantité de ressources qui seraient coûteuses à copier puis à supprimer. Au lieu de cela, votre implémentation personnalisée pourrait simplement échanger les poignées ou les pointeurs nécessaires pour effectuer l'échange.

Avec l'avènement de std::move et les types mobiles (et implémenté votre type en tant que tel), vers C++ 11 et versions ultérieures, une grande partie de la justification initiale commence à s'effondrer; mais néanmoins, si un swap personnalisé est meilleur que le standard, implémentez-le.

Le code générique pourra généralement utiliser votre swap personnalisé s'il utilise le mécanisme ADL de manière appropriée.

57
Niall

Comment la fonction de swap est-elle implémentée dans la STL?

Quelle implémentation? C'est une spécification, pas une seule bibliothèque concrète. Si vous voulez dire comment fait la bibliothèque standard de mon compilateur, dites-nous de quel compilateur il s'agit, ou lisez le code vous-même.

Est-ce aussi simple que cela:

C'est essentiellement la version naïve pré-C++ 11.

Cette implémentation non spécialisée force une copie: pour T = std::vector<SomethingExpensive> dans votre exemple, le code se traduit par:

template<typename T> void swap(T& t1, T& t2) {
  T tmp(t1); // duplicate t1, making an expensive copy of each element
  t1=t2;     // discard the original contents of t1,
             // and replace them with an expensive duplicate of t2
  t2=tmp;    // discard the original contents of t2,
             // and replace them with an expensive duplicate of tmp
}            // implicitly destroy the expensive temporary copy of t1

afin d'échanger deux vecteurs, nous avons essentiellement créé trois. Il y avait trois allocations dynamiques et beaucoup d'objets coûteux copiés, et n'importe laquelle de ces opérations pouvait être lancée, laissant peut-être les arguments dans un état indéterminé.

Comme c'était évidemment horrible, des surcharges étaient prévues pour les conteneurs coûteux, et on vous encourageait à écrire des surcharges pour vos propres types coûteux: par exemple. le std::vector la spécialisation avait accès aux internes du vecteur, et pouvait échanger deux vecteurs sans toute la copie:

template <typename T> void swap(vector<T> &v1, vector<T> &v2) { v1.swap(v2); }
template <typename T> void vector<T>::swap(vector<T>& other) {
  swap(this->size_, other.size_); // cheap integer swap of allocated count
  swap(this->used_, other.used_); // cheap integer swap of used count
  swap(this->data__, other.data_); // cheap pointer swap of data ptr
}

Notez que cela n'implique aucune copie de quelque chose de coûteux, aucune allocation (dé) dynamique et est garanti de ne pas jeter.

Maintenant, la raison de cette spécialisation est que vector :: swap a accès aux éléments internes de vector et peut les déplacer en toute sécurité et efficacement sans copier.

Pourquoi aurais-je besoin de faire cela [en me spécialisant ... pour votre propre classe]?

Pre-C++ 11, pour la même raison que std::vector - pour rendre l'échange efficace et sans exception.

Depuis C++ 11, ce n'est vraiment pas le cas - si vous fournissez la construction et l'affectation des mouvements, ou si le compilateur peut générer des valeurs par défaut saines pour vous.

Le nouveau swap générique:

template <typename T> void swap(T& t1, T& t2) {
    T temp = std::move(t1);
    t1 = std::move(t2);
    t2 = std::move(temp);
}

peut utiliser la construction/affectation de déplacement pour obtenir essentiellement le même comportement que l'implémentation vectorielle personnalisée ci-dessus, sans avoir besoin d'écrire une implémentation personnalisée du tout.

18
Useless