2011-06-20 10 views
29

Recientemente, manyquestionspop up sobre cómo proporcionar su propia función swap. Con C++ 11, std::swap usará std::move y mover semántica para intercambiar los valores dados lo más rápido posible. Esto, por supuesto, solo funciona si proporciona un constructor de movimiento y un operador de asignación de movimiento (o uno que use el valor de paso por valor).Semántica de movimiento == función de intercambio personalizada obsoleta?

Ahora, con eso dado, ¿es realmente necesario escribir sus propias funciones swap en C++ 11? Solo pude pensar en tipos no móviles, pero una vez más, los swap personalizados generalmente 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 ...

Respuesta

19

Es una cuestión de criterio. Por lo general, dejaré que std::swap haga el trabajo para el código de creación de prototipos, pero para el código de versión, escriba un intercambio personalizado. Normalmente puedo escribir un intercambio personalizado que es aproximadamente dos veces más rápido que 1 movimiento de construcción + 2 asignaciones de movimiento + 1 destrucción sin recursos. Sin embargo, es posible que desee esperar hasta que std::swap demuestre ser un problema de rendimiento antes de tomarse la molestia.

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 de rvalues, y si es noexcept, entonces swap será noexcept. Ej .:

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

std::swap<A> es noexcept, incluso sin mover los miembros.

+0

Gracias por la actualización, Howard. Supongo que 'A (const A &) noexcept;' es un error tipográfico, o tal vez tragar símbolos? Es decir. que usted quiso decir 'A (A &&) noexcept;' (y lo mismo para el operador de asignación)? Cheers, –

+3

@Alf: No; él está dando un ejemplo donde 'std :: swap' será' noexcept (true) 'incluso sin una construcción/asignación de movimiento real. –

+0

@Dennis: Creo que necesito más explicación, porque para mí eso se parece a un constructor de copia ordinario (no para mover) y a un operador de asignación de copias ordinario, y según tengo entendido, no tienen nada que ver con 'is_nothrow_move_constructible' o' is_nothrow_move_assignable'? –

1

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

+1

std :: array son copiables pero no tienen constructor de movimiento, si eso es lo que quieres decir. –

+0

Una cosa no movible: 'CRITICAL_SECTION' de Win32 API se parece a uno. Es un tipo opaco, debe inicializarse, no puede copiarse y tiene un puntero dentro. El envoltorio móvil alrededor de uno debería tener un puntero para administrarlo. –

0

Por convención, una costumbre swap ofrece garantía de no-tiro. 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 duck como bug, o maniobras similares del juego de palabras políticas. Así que no confiaría en ninguna respuesta aquí a menos que brinde un detallado golpe por golpe citando desde C++ 0x to-be-standard, hasta el más mínimo detalle (para asegurarse de que no bug).

0

Sin duda, puede poner en práctica de intercambio 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, por ejemplo A, que podemos cambiar 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 el destructor, simplemente intercambia los punteros (que bien puede ser implementado como XCHG o algo similar).

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

+0

Si su constructor de movimiento/operador de asignación tiene efectos secundarios no reflejados en su intercambio personalizado, sospecho que los ha implementado incorrectamente. –

0

considerar las siguientes clase que tiene un recurso de memoria asignada (por simplicidad, representada 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); } 

Entonces std::swap resultados en borrado de puntero nulo 3 veces (tanto para movimiento operador de asignación y operador de asignación unificadora cajas). Los compiladores pueden tener problemas para optimizar un delete, consulte https://godbolt.org/g/E84ud4.

personalizado swap no llama ningún delete y puede ser por lo tanto más eficiente. Supongo que esta es la razón por la cual std::unique_ptr ofrece una especialización personalizada de std::swap.

ACTUALIZACIÓN

Parece que los compiladores de Intel y Clang son capaces de optimizar a cabo la eliminación de punteros nulos, sin embargo no es GCC. Ver Why GCC doesn't optimize out deletion of null pointers in C++? para más detalles.

ACTUALIZACIÓN

Parece que con GCC, podemos evitar la invocación de delete operador reescribiendo X de la siguiente manera:

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