La sémantique des mouvements = = fonction de swap personnalisée obsolète?
Récemment, beaucoup questions pop-up sur la façon de fournir votre propre swap
de la fonction. Avec C++11, std::swap
utilisera std::move
et déplacera la sémantique pour échanger les valeurs données aussi vite que possible. Ceci, bien sûr, ne fonctionne que si vous fournissez un constructeur de mouvements et un opérateur d'affectation de mouvements (ou un qui utilise la valeur de passage).
maintenant, avec cela donné, est-il réellement nécessaire de Ecrire vos propres fonctions swap
en C++11? Je ne pouvais penser que des types non mobiles, mais encore une fois, la coutume swap
s travaillent généralement par une sorte de "l'échange de pointeur" (alias en mouvement). Peut-être avec certaines variables de référence? Hm...
5 réponses
C'est une question de jugement. Je vais généralement laisser std::swap
faire le travail pour le code de prototypage, mais pour le code de publication écrire un swap personnalisé. Je peux habituellement écrire un swap personnalisé qui est environ deux fois plus rapide que 1 construction de mouvement + 2 affectations de mouvement + 1 destruction sans ressources. Toutefois, on peut vouloir attendre jusqu'à ce que std::swap
s'avère effectivement être un problème de performance avant d'aller à la peine.
mise à jour pour Alf P. Steinbach:
20.2.2 [utilité.swap] spécifie que std::swap(T&, T&)
a un noexcept
équivalent à:
template <class T>
void
swap(T& a, T& b) noexcept
(
is_nothrow_move_constructible<T>::value &&
is_nothrow_move_assignable<T>::value
);
i. e. si les opérations de déménagement sur T
sont noexcept
, alors std::swap
sur T
est noexcept
.
notez que cette spécification ne nécessite pas de membres move. Il exige seulement que la construction et la cession de rvalues existe, et si elle est noexcept
, alors l'échange sera noexcept
. E. g.:
class A
{
public:
A(const A&) noexcept;
A& operator=(const A&) noexcept;
};
std::swap<A>
n'est pas exclu, même sans membres mobiles.
il peut y avoir certains types qui peuvent être échangés mais pas déplacés. Je ne connais pas de types non mobiles, donc je n'ai pas d'exemples.
par convention une coutume swap
offre une garantie sans lancer. Je ne sais pas pour std::swap
. Mon impression des travaux de la Commission à ce sujet est qu'il s'agit d'un travail tout à fait politique, et je ne serais donc pas surpris qu'ils aient défini quelque part duck
comme bug
, ou des manœuvres de jeu de mots politiques similaires. Donc, je ne m'en remettrai pas à pour toute réponse ici à moins qu'il ne fournisse un coup détaillé en citant du C++0x to-be-standard, dans les moindres détails (de sorte que pour être sûr pas bug
).
bien sûr, vous pouvez implémenter le swap comme
template <class T>
void swap(T& x, T& y)
{
T temp = std::move(x);
x = std::move(y);
y = std::move(temp);
}
mais nous pourrions avoir notre propre classe, disons A
, que nous pouvons échanger plus rapidement.
void swap(A& x, A& y)
{
using std::swap;
swap(x.ptr, y.ptr);
}
qui, au lieu d'avoir à exécuter un constructeur et un destructeur, change simplement les pointeurs (qui peuvent très bien être implémentés comme XCHG ou quelque chose de similaire).
bien sûr, le compilateur pourrait optimiser les appels constructeur / destructeur dans le premier exemple, mais s'ils avoir des effets secondaires (c.-à-d. appels à nouveau/Supprimer) il peut ne pas être assez intelligent pour les optimiser loin.
considérer la classe suivante qui détient une ressource de mémoire-attribuée (pour la simplicité, représentée par un nombre entier simple):
class X {
int* i_;
public:
X(int i) : i_(new int(i)) { }
X(X&& rhs) noexcept : i_(rhs.i_) { rhs.i_ = nullptr; }
// X& operator=(X&& rhs) noexcept { delete i_; i_ = rhs.i_;
// rhs.i_ = nullptr; return *this; }
X& operator=(X rhs) noexcept { swap(rhs); return *this; }
~X() { delete i_; }
void swap(X& rhs) noexcept { std::swap(i_, rhs.i_); }
};
void swap(X& lhs, X& rhs) { lhs.swap(rhs); }
puis std::swap
se traduit par suppression du pointeur nul 3 fois (à la fois pour les cas move assignment operator et unifying assignment operator ). Les compilateurs pourraient avoir des problèmes pour optimiser un tel delete
, voir https://godbolt.org/g/E84ud4 .
Custom swap
n'appelle aucun delete
et pourrait donc être plus efficace. Je suppose que c'est la raison pour laquelle std::unique_ptr
fournit la spécialisation personnalisée std::swap
.
mise à JOUR
il semble que les compilateurs Intel et Clang sont capables d'optimiser la suppression des pointeurs null, mais GCC ne l'est pas. Voir optimiser la suppression des pointeurs null en C++? pour plus de détails.
mise à JOUR
il semble qu'avec GCC, nous pouvons éviter d'invoquer l'opérateur delete
en réécrivant X
comme suit:
// X& operator=(X&& rhs) noexcept { if (i_) delete i_; i_ = rhs.i_;
// rhs.i_ = nullptr; return *this; }
~X() { if (i_) delete i_; }