compiler c++ c++11 swap move-semantics obsolete

c++ - compiler - Mover la semántica== función de intercambio personalizado obsoleto?



forward c++ (5)

Recientemente, surgen many questions sobre cómo proporcionar su propia función de swap . Con C ++ 11, std::swap usará std::move y move semantics para intercambiar los valores dados lo más rápido posible. Esto, por supuesto, solo funciona si usted proporciona un constructor de movimientos y un operador de asignación de movimientos (o uno que use el paso por valor).

Ahora, con eso dado, ¿es realmente necesario escribir sus propias funciones de swap en C ++ 11? Solo pude pensar en tipos no movibles, pero una vez más, los swap personalizados usualmente funcionan a través de algún tipo de "intercambio de puntero" (también conocido como movimiento). Tal vez con ciertas variables de referencia? Hm ...


Claro, puedes implementar swap como

template <class T> void swap(T& x, T& y) { T temp = std::move(x); x = std::move(y); y = std::move(temp); }

Pero podríamos tener nuestra propia clase, digamos A , que podemos intercambiar más rápidamente.

void swap(A& x, A& y) { using std::swap; swap(x.ptr, y.ptr); }

Que, en lugar de tener que ejecutar un constructor y un destructor, simplemente intercambia los punteros (que bien pueden implementarse como XCHG o algo similar).

Por supuesto, el compilador podría optimizar las llamadas de constructor / destructor en el primer ejemplo, pero si tienen efectos secundarios (es decir, llamadas a nuevo / eliminar) puede que no sea lo suficientemente inteligente como para optimizarlas.


Considere la siguiente clase que contiene un recurso asignado a la memoria (por simplicidad, representado por un solo número entero):

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); }

Luego, std::swap da como resultado la eliminación del puntero nulo 3 veces (tanto para el operador de asignación de movimientos como para los casos de operadores de asignación de unidades ). Los compiladores pueden tener problemas para optimizar dicha delete , consulte https://godbolt.org/g/E84ud4 .

El swap personalizado no llama a ninguna delete y, por lo tanto, podría ser más eficiente. Supongo que esta es la razón por la que std::unique_ptr ofrece especialización std::swap .

ACTUALIZAR

Parece que los compiladores de Intel y Clang son capaces de optimizar la eliminación de los punteros nulos, sin embargo, GCC no lo es. Consulte ¿Por qué GCC no optimiza la eliminación de los punteros nulos en C ++? para detalles.

ACTUALIZAR

Parece que con GCC, podemos evitar invocar el operador de delete reescribiendo X siguiente manera:

// X& operator=(X&& rhs) noexcept { if (i_) delete i_; i_ = rhs.i_; // rhs.i_ = nullptr; return *this; } ~X() { if (i_) delete i_; }


Es una cuestión de juicio. Normalmente dejaré que std::swap haga el trabajo para el código de creación de prototipos, pero para el código de lanzamiento escriba un intercambio personalizado. Normalmente puedo escribir un intercambio personalizado que es aproximadamente el doble de rápido que 1 construcción de movimientos + 2 asignaciones de movimientos + 1 destrucción sin recursos. Sin embargo, uno puede querer esperar hasta que std::swap realmente demuestre ser un problema de rendimiento antes de molestar.

Actualización para Alf P. Steinbach:

20.2.2 [utility.swap] especifica que std::swap(T&, T&) tiene un noexcept equivalente a:

template <class T> void swap(T& a, T& b) noexcept ( is_nothrow_move_constructible<T>::value && is_nothrow_move_assignable<T>::value );

Es decir, si las operaciones de movimiento en T son noexcept , entonces std::swap en T es noexcept .

Tenga en cuenta que esta especificación no requiere mover miembros. Solo requiere que exista construcción y asignación a partir de valores, y si es noexcept , entonces swap será noexcept . P.ej:

class A { public: A(const A&) noexcept; A& operator=(const A&) noexcept; };

std::swap<A> es noexcept, incluso sin miembros de movimiento.


Por convención un swap personalizado ofrece garantía de no tirar. No sé sobre std::swap . Mi impresión del trabajo del comité sobre eso es que todo era político, por lo que no me sorprendería si en algún lugar hubieran definido el duck como un bug o maniobras políticas similares. Por lo tanto, no confiaría en ninguna respuesta aquí a menos que proporcione un resumen detallado de C ++ 0x para ser estándar, hasta el más mínimo detalle (para estar seguro de que no haya ningún bug ).


Puede haber algunos tipos que se pueden intercambiar pero no mover. No sé de ningún tipo no móvil, así que no tengo ningún ejemplo.