lenguaje - ¿Por qué no hay métodos swap() generados por el compilador en C++ 0x?
hola mundo c++ (4)
¿Alguien sabe si tales cosas (¿locura?) Han sido sugeridas al comité de estándares de C ++
Enviar un correo electrónico a Bjarne. Él sabe todo esto y generalmente responde dentro de un par de horas.
Los compiladores C ++ generan automáticamente constructores de copia y operadores de copia-asignación. ¿Por qué no swap
también?
En la actualidad, el método preferido para implementar el operador de copia de tareas es la expresión copiar y cambiar:
T& operator=(const T& other)
{
T copy(other);
swap(copy);
return *this;
}
( ignorando la forma amigable de copia-elisión que usa el valor de paso por valor ).
Este modismo tiene la ventaja de ser transaccional frente a las excepciones (suponiendo que la implementación del swap
no se produce). Por el contrario, el operador predeterminado de copia-asignación generado por el compilador recurrentemente hace la copia-asignación en todas las clases base y miembros de datos, y eso no tiene la misma excepción-garantías de seguridad.
Mientras tanto, implementar métodos de swap
manualmente es tedioso y propenso a errores:
- Para garantizar que el
swap
no se produzca, debe implementarse para todos los miembros que no pertenecen a POD en la clase y en las clases base, en sus miembros que no pertenecen a POD, etc. - Si un mantenedor agrega un nuevo miembro de datos a una clase, el mantenedor debe recordar modificar el método de
swap
esa clase. No hacerlo puede introducir errores sutiles. Además, dado que elswap
es un método ordinario, los compiladores (al menos ninguno que conozca) no emiten advertencias si la implementación delswap
es incompleta.
¿No sería mejor si el compilador generó métodos de swap
automáticamente? Entonces la implementación implícita de copia-asignación podría aprovecharlo.
La respuesta obvia es probablemente: el modismo copiar y cambiar no existía cuando se desarrolló C ++, y hacer esto ahora podría romper el código existente.
Aún así, tal vez la gente podría optar por dejar que el compilador genere swap
usando la misma sintaxis que C ++ 0x usa para controlar otras funciones implícitas:
void swap() = default;
y luego podría haber reglas:
- Si hay un método de
swap
generado por el compilador, se puede implementar un operador implícito de copia y asignación usando copy-and-swap. - Si no hay
swap
método deswap
generado por el compilador, se implementaría un operador implícito de asignación de copia como antes (invocando la asignación de copia en todas las clases base y en todos los miembros).
¿Alguien sabe si tales cosas (locuras?) Han sido sugeridas al comité de estándares de C ++, y si es así, ¿qué opiniones tenían los miembros del comité?
¿Está planeado incluso el constructor / asignación de movimiento generado por el compilador (con la palabra clave predeterminada )?
Si hay un método de intercambio generado por el compilador, se puede implementar un operador implícito de copia y asignación usando copy-and-swap.
A pesar de que el modismo no cambia el objeto en caso de excepciones, ¿no es necesario que este modismo, al requerir la creación de un tercer objeto, aumente las posibilidades de falla?
También puede haber implicaciones de rendimiento (la copia puede ser más costosa que la "asignación") y es por eso que no veo que el compilador pueda implementar una funcionalidad tan complicada.
En general, no sobrecargo operador = y no me preocupo por este nivel de seguridad de excepción: no envuelvo las asignaciones individuales en bloques de prueba. ¿Qué haría con el std::bad_alloc
más probable en ese punto? - entonces no me importaría si el objeto, antes de que termine destruido de todos modos, permaneciera en el estado original o no. Por supuesto, puede haber situaciones específicas en las que de hecho lo necesite, pero no veo por qué el principio de "usted no paga por lo que no usa" debe abandonarse aquí.
Esto es adicional a la respuesta de Terry.
La razón por la que tuvimos que realizar funciones de swap
en C ++ antes de 0x se debe a que el estándar de libre función std::swap
fue menos eficiente (y menos versátil) de lo que podría ser. Hizo una copia de un parámetro, luego tuvo dos reasignaciones y luego lanzó la copia esencialmente desperdiciada. Hacer una copia de una clase de peso pesado es una pérdida de tiempo, cuando nosotros como programadores sabemos que todo lo que realmente necesitamos hacer es intercambiar los punteros internos y demás.
Sin embargo, las referencias rvalue alivian esto por completo. En C ++ 0x, el swap
se implementa como:
template <typename T>
void swap(T& x, T& y)
{
T temp(std::move(x));
x = std::move(y);
y = std::move(temp);
}
Esto tiene mucho más sentido. En lugar de copiar datos, solo estamos moviendo datos. Esto incluso permite el intercambio de tipos no copiables, como las secuencias. El borrador del estándar C ++ 0x establece que para que los tipos se intercambien con std::swap
, deben ser constructables rvalue, y asignables a rvalue (obviamente).
Esta versión de swap
básicamente hará lo que haría cualquier función de intercambio escrita personalizada. Considere una clase para la que normalmente escribiríamos swap
(como este vector "tonto"):
struct dumb_vector
{
int* pi; // lots of allocated ints
// constructors, copy-constructors, move-constructors
// copy-assignment, move-assignment
};
Anteriormente, el swap
haría una copia redundante de todos nuestros datos, antes de descartarlo más tarde. Nuestra función de swap
personalizado simplemente cambiaría el puntero, pero puede ser torpe para usar en algunos casos. En C ++ 0x, mover logra el mismo resultado final. Llamar a std::swap
generaría:
dumb_vector temp(std::move(x));
x = std::move(y);
y = std::move(temp);
Que se traduce a:
dumb_vector temp;
temp.pi = x.pi; x.pi = 0; // temp(std::move(x));
x.pi = y.pi; y.pi = 0; // x = std::move(y);
y.pi = temp.pi; temp.pi = 0; // y = std::move(temp);
El compilador, por supuesto, se deshará de las asignaciones redundantes, dejando:
int* temp = x.pi;
x.pi = y.pi;
y.pi = temp;
Que es exactamente lo que nuestro swap
personalizado habría hecho en primer lugar. Entonces, antes de C ++ 0x estaría de acuerdo con su sugerencia, ya que los swap
personalizados ya no son necesarios, con la introducción de referencias rvalue. std::swap
funcionará perfectamente en cualquier clase que implemente funciones de movimiento.
De hecho, yo diría que implementar una función de swap
debería convertirse en una mala práctica. Cualquier clase que necesitaría una función de swap
también necesitaría funciones rvalue. Pero en ese caso, simplemente no hay necesidad del desorden de un swap
personalizado. El tamaño del código aumenta (dos funciones ravlue contra un swap
), pero las referencias rvalue no solo se aplican para el intercambio, sino que nos deja una compensación positiva. (Código más rápido en general, una interfaz más limpia, un poco más de código, no más problemas de ADL de swap
).
En cuanto a si podemos o no las funciones de default
por default
, no lo sé. Lo buscaré más tarde o tal vez alguien más pueda sonar, pero eso sería útil. :)
Aun así, tiene sentido permitir funciones rvalue default
lugar de swap
. Entonces, en esencia, siempre y cuando permitan = default
funciones rvalue = default
, su solicitud ya ha sido hecha. :)
EDITAR: Hice un poco de búsqueda, y la propuesta para = default
movimiento = default
fue la propuesta n2583
. De acuerdo con this (que no sé leer muy bien), fue "retrocedido". Se enumera en la sección titulada "No está listo para C ++ 0x, pero está abierto para volver a enviarlo en el futuro". Parece que no formará parte de C ++ 0x, pero puede agregarse más tarde.
Un poco decepcionante. :(
EDIT 2: Mirando un poco más, encontré esto: Definir funciones especiales para mover miembros, que es mucho más reciente, y parece que podemos move
defecto. ¡Hurra!
swap, cuando es usado por algoritmos STL, es una función libre . Hay una implementación de intercambio predeterminada: std::swap
. Hace lo obvio. Parece que tiene la impresión de que si agrega una función de miembro de intercambio a su tipo de datos, los contenedores STL y los algoritmos lo encontrarán y usarán. Este no es el caso.
Se supone que debes especializar std :: swap (en el espacio de nombres al lado de tu UDT, para que lo encuentre ADL) si puedes hacerlo mejor. Es idiomático hacer que difiera a una función de intercambio de miembros.
Mientras estamos en el tema, también es idiomático en C ++ 0x (en la medida de lo posible, tener modismos en un nuevo estándar) para implementar constructores de rvalue como un intercambio.
Y sí, en un mundo donde un intercambio de miembros era el diseño del lenguaje en lugar de un intercambio de funciones libre, esto implicaría que necesitaríamos un operador de intercambio en lugar de una función, o tipos primitivos (int, float, etc.) couldn '' ser tratado genéricamente (ya que no tienen intercambio de funciones miembro). Entonces, ¿por qué no hicieron esto? Tendría que preguntar a los miembros del comité con certeza, pero estoy 95% seguro de que la razón es que el comité siempre ha preferido la implementación de características de la biblioteca siempre que sea posible, sobre inventar una nueva sintaxis para implementar una función. La sintaxis de un operador de intercambio sería extraña, porque a diferencia de =, +, -, etc. y de todos los demás operadores, no existe un operador algebraico con el que todos estén familiarizados para el "intercambio".
C ++ es sintácticamente lo suficientemente complejo. Hacen todo lo posible para no agregar nuevas palabras clave o funciones de sintaxis siempre que sea posible, y solo lo hacen por muy buenas razones (¡lambdas!).