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...

30
demandé sur Community 2011-06-20 23:30:40

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.

21
répondu Howard Hinnant 2017-12-02 13:23:37

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.

1
répondu Puppy 2011-06-20 19:41:20

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 ).

0
répondu Cheers and hth. - Alf 2011-06-20 21:10:21

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.

0
répondu Clinton 2011-06-21 04:41:31

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_; }
0
répondu Daniel Langr 2017-08-15 12:12:53