2010-01-16 14 views
41

Los compiladores de C++ generan automáticamente constructores de copia y operadores de asignación de copia. ¿Por qué no también swap?¿Por qué no hay métodos swap() generados por el compilador en C++ 0x?

Estos días el método preferido para la aplicación del operador de copia-asignación es el idioma de copiar y swap:

T& operator=(const T& other) 
{ 
    T copy(other); 
    swap(copy); 
    return *this; 
} 

(ignoring the copy-elision-friendly form that uses pass-by-value).

Esta expresión idiomática tiene la ventaja de ser transaccional frente a excepciones (suponiendo que la implementación swap no arroja). 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, la implementación de swap métodos manual es tedioso y propenso a errores:

  1. Para garantizar que swap no tira, se debe implementar para todos los miembros no pod en la clase y en las clases de base, en sus miembros no POD, etc.
  2. Si un mantenedor agrega un nuevo miembro de datos a una clase, el mantenedor debe recordar modificar el método swap de esa clase. No hacerlo puede introducir errores sutiles. Además, dado que swap es un método ordinario, los compiladores (al menos ninguno que conozco) no emiten advertencias si la implementación swap está incompleta.

¿No sería mejor si el compilador generó los métodos 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.

Sin embargo, tal vez la gente podría optar por dejar que el compilador generar swap utilizando la misma sintaxis que C++ 0x utiliza para controlar otras funciones implícitas:

void swap() = default; 

y entonces no puede haber reglas:

  1. Si hay un método swap generado por el compilador, se puede implementar un operador implícito de copia-asignación utilizando copy-and-swap.
  2. Si no hay un método swap generado por el compilador, se implementará un operador implícito de copia-asignación como antes (invocando la asignación de copia en todas las clases base y en todos los miembros).

¿Alguien sabe si tales cosas (locuras?) Se han sugerido al comité de estándares de C++, y si es así, ¿qué opiniones tenían los miembros del comité?

+0

me he preguntado lo mismo una serie de.! veces, especialmente porque 'swap' generalmente se implementa en términos de' intercambio' de todos los miembros de datos de todos modos, por lo que es muy parecido a 'copiar' o' asignación' y las nuevas versiones de 'movimiento' –

+7

En C++ 0x,' swap 'debe ser algo así como' template void swap (T & x, T & y) {T temp (std :: mover (x)); x = std :: move (y); y = std :: move (temp);} 'que esencialmente hará lo que cualquier función de intercambio escrita personalizada haría. Estoy de acuerdo con su pregunta, pero los cambios personalizados ya no son necesarios, con el ction de referencias r-value. 'std :: swap' funcionará muy bien. – GManNickG

+4

Sospecho que la razón por la que no está en C++ 98 es que las únicas funciones generadas por el compilador en C++ están ahí para hacer que las clases se comporten como estructuras C: puedes declararlas, destruirlas y copiarlas. Por muy útil que sea un 'swap' predeterminado o' operator '' por defecto (y ambos podrían implementarse mediante operaciones de campo) el estándar C++ quería un mínimo de funciones predeterminadas, por lo que hay menos para suprimir si no lo quiere . La sintaxis C++ 0x 'function = something' podría ser una opción mucho mejor, ya que es opcional. En principio, podría tener incluso diferentes opciones de canjes seguros y no seguros. –

Respuesta

22

Esto se suma a la respuesta de Terry.

La razón por la que tuvimos que realizar swap funciones en C++ antes de 0x es porque la función libre general 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 lo alivian por completo. En C++ 0x, 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 hace 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 asignados de manera r (obviamente).

Esta versión de swap esencialmente hará lo que cualquier función de intercambio escrita personalizada haría. Considere una clase nos gustaría normalmente escribimos para swap (como este vector "tonto"):

struct dumb_vector 
{ 
    int* pi; // lots of allocated ints 

    // constructors, copy-constructors, move-constructors 
    // copy-assignment, move-assignment 
}; 

Anteriormente, swap haría una copia redundante de todos nuestros datos, antes de deshacerse de él más tarde. Nuestra función personalizada swap simplemente cambiaría el puntero, pero puede ser torpe para usar en algunos casos. En C++ 0x, mover logra el mismo resultado final. Llamando std::swap generaría:

dumb_vector temp(std::move(x)); 
x = std::move(y); 
y = std::move(temp); 

que se traduce en:

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 deshacerse de asignación de redundante, dejando:

int* temp = x.pi; 
x.pi = y.pi; 
y.pi = temp; 

Cuál es exactamente lo nuestro personalizado swap habría hecho en primer lugar. Entonces, antes de C++ 0x, estaría de acuerdo con su sugerencia, ya que los swap ya no son necesarios, con la introducción de las referencias rvalue. std::swap funcionará perfectamente en cualquier clase que implemente funciones de movimiento.

De hecho, yo diría que implementar una función swap debería convertirse en una mala práctica. Cualquier clase que necesitaría una función 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 uno 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, interfaz más limpia, un poco más código, no más swap ADL molestia.)

En cuanto a si podemos o no default funciones rvalue, no sé. Lo buscaré más tarde o tal vez alguien más pueda sonar, pero eso sería útil. :)

Aún así, tiene sentido permitir default funciones rvalue en lugar de swap. Entonces, en esencia, siempre que permitan = default funciones de valor, su solicitud ya se ha realizado. :)

EDIT: Hice un poco de búsqueda, y la propuesta para mover = 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 a su alrededor un poco más, me encontré con esto: Defining Move Special Member Functions que es mucho más reciente, y tiene un aspecto como podemos defecto move Yay

+0

Eso tiene sentido. No me he acostumbrado a la nueva referencia de valor y sospecho que podría ser un factor. Por lo poco que he leído, parece que las clases necesitarán implementar explícitamente un constructor de movimientos, que tendría cargas de mantenimiento similares a la implementación de 'swap', a menos que haya una manera de optar por una definición implícita con'. .. = default'. Supongo que incluso si no lo hubiera, al menos los compiladores podrían tener más facilidad para generar advertencias para implementaciones incompletas. – jamesdlin

+0

@james Sí, en realidad es más una carga ya que necesita un operador de movimiento y constructor de movimiento. Probablemente sea una buena compensación para todas las demás cosas que proporcionan las referencias de valor real. Realmente espero que podamos hacer '' default' r-value functions. – GManNickG

+0

¿Los constructores y las asignaciones de rvalue tienen que ser nothrow, o simplemente se recomiendan encarecidamente (para obtener un nothrow 'std :: swap' para su clase)? Por ejemplo, si tiene como miembro una clase heredada sin valor agregado, entonces podría estar tentado de escribir un valor de corrección que mueva la mayoría de los datos pero copie ese bit. Eso es la muerte para la implementación de intercambio predeterminada que ofrece, ni siquiera sería fuertemente seguro, y mucho menos nothrow. Entonces, a menos que me haya olvidado de algo astuto, debe implementar el intercambio, para llamar al intercambio en el miembro heredado, mala práctica o no. –

2

¿Alguien sabe si este tipo de cosas (? Locas) se ha sugerido que el 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.

+5

O, mucho mejor, publique la pregunta en Usenet en comp.std.C++ que es el principal punto de debate público para C++ estándar y los procesos de estandarización. – Richard

10

swap, cuando se utiliza mediante algoritmos STL, es una función gratuita. 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.

Si bien estamos en el tema, también es idiomático en C++ 0x (en la medida de lo posible para 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.) no pueden tratarse genéricamente (ya que no tienen un swap de función 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 lo suficientemente complejo desde el punto de vista sintáctico. 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!).

+4

UDT, ADL ... WTF? – ergosys

+22

tipo definido por el usuario, búsqueda dependiente del argumento, qué f ** k –

+0

No estoy bajo tal impresión. No estoy hablando de la función independiente 'std :: swap' ni de los contenedores o algoritmos STL en absoluto, sino de proporcionar funciones de miembros' swap' automáticas a los objetos, porque para el compilador es más fácil hacer eso que los humanos. Dicho esto, me doy cuenta de que el solo hecho de proporcionar la función de miembro no permitiría una sintaxis consistente, por lo que si se necesita consistencia, probablemente necesitaría, por ejemplo, su propio operador. – jamesdlin

1

¿Está planificado/asignación de movimientos generados por el compilador (con la palabra clave predeterminada )?

Si hay un método de intercambio generado por el compilador, un implícito operador copia-asignación se puede implementar utilizando de copiar y de intercambio.

Aunque la expresión idiomática deja el objeto sin cambios en caso de excepciones, ¿no es necesario que la fraseología, 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.

Generalmente no sobrecargo operator = y no me preocupo por este nivel de seguridad de las excepciones: 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í.

+0

"no paga por lo que no usa". Si nunca detecta excepciones, y nunca escribe destructores que asuman que los objetos están en un estado válido, entonces no necesita garantías de excepción. Otros usuarios de sus clases pueden detectar excepciones a veces y hacer cosas en destructores. Necesitan garantías de excepción. Lamentablemente, no siempre es posible proporcionar garantías de excepción para que nunca pague si no las usa. Afortunadamente, si su clase tiene recursos, y en la asignación tendría que liberar el recurso anterior y asignar uno nuevo, entonces CAS no implica gastos adicionales. –

Cuestiones relacionadas