2010-08-26 10 views
7

Tengo una clase compleja de C++ que contiene un conjunto de datos leídos del disco. Contiene una mezcla ecléctica de flotadores, ints y estructuras y ahora es de uso general. Durante una importante revisión de código, se preguntó si tenemos un operador de asignación personalizado o si confiamos en la versión generada por el compilador y, de ser así, ¿cómo sabemos que funciona correctamente? Bueno, no escribimos una asignación personalizada y así se le añadió una unidad de prueba para comprobar que si hacemos:Operador de asignación de C++: ¿compilador generado o personalizado?

CalibDataSet datasetA = getDataSet(); 
CalibDataSet dataSetB = datasetA; 

continuación datasetB es la misma que datasetA. Un par de cientos de líneas más o menos. Ahora el cliente insiste en que no podemos confiar en que el compilador (gcc) sea correcto para futuras versiones y deberíamos escribir el nuestro. ¿Están en lo cierto al insistir en esto?

Otros detalles:

Estoy impresionado por las respuestas/comentarios ya publicados y la forma time.Another respuesta de esta pregunta podría ser: ¿Cuándo una estructura POD/clase se convierta en un 'no' POD estructura/clase?

+3

Tenga en cuenta que su ejemplo de código no invoca al operador de asignación de copias. Invoca el constructor de copia. El '=' aquí no es una tarea, es una inicialización. Necesitaría algo similar a 'CalibDataSet dataSetB; dataSetB = datasetA; 'para invocar el operador de asignación de copia. –

+0

Bueno, eso depende. Mostrar la definición de CalibDataSet. ¿Administra el recurso? –

+4

Regla de oro: si todo en su clase puede y debe ser copiado por 'a.thing = b.thing', entonces el operador autogenerado probablemente esté bien. –

Respuesta

13

Es bien conocido lo que hará el operador de asignación generado automáticamente, eso se define como parte del estándar y un compilador C++ siempre compatible generará un operador de asignación de comportamiento correcto (si no lo hizo, entonces no sería un compilador que cumpla con los estándares).

Normalmente solo necesita escribir su propio operador de asignación si ha escrito su propio destructor o constructor de copia. Si no los necesita, entonces tampoco necesita un operador de asignación.

+9

Esto se llama regla de tres. Si su clase maneja cualquier recurso (un indicador que sí lo es, libere algún recurso manualmente en el destructor), debe asegurarse de que la semántica de copia funcione. Use la [copia e intercambio de idioma] (http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom). – GManNickG

0

Supongo que puede aparecer el problema de la copia profunda de poca profundidad en caso de que se trate de cadenas. http://www.learncpp.com/cpp-tutorial/912-shallow-vs-deep-copying/

pero creo que siempre es recomendable escribir sobrecargas de asignación para las clases definidas por el usuario.

+4

No estoy de acuerdo con usted aquí; debe conocer sus clases y escribir los operadores cuando sea necesario.Escribirlos innecesariamente los convierte en código que debes mantener: si el contenido de la clase no necesita un operador de asignación personalizado, escribir uno creará más errores (a largo plazo) que no escribir uno. –

2

Si el compilador no está generando la asignación correctamente, entonces tiene problemas más grandes de los que preocuparse que implementar la sobrecarga de tareas (como el hecho de que tiene un compilador roto). A menos que su clase contenga punteros, no es necesario proporcionar su propia sobrecarga; sin embargo, es razonable solicitar una sobrecarga explícita, no porque el compilador pueda romperse (lo que es absurdo), sino más bien para documentar su intención de que la asignación sea permitida y se comporte de esa manera. En C++ 0x, será posible documentar el intento y ahorrar tiempo utilizando = default para la versión generada por el compilador.

+0

Interesante. Espero C++ 0x – ExpatEgghead

+0

"es razonable solicitar una sobrecarga explícita" - ummm ... no. Un comentario sería suficiente, es menos propenso a errores y puede ser más eficiente. La mejor práctica actual de C++ funciona exactamente de la otra manera: se debe usar un operador de asignación privada para garantizar que no sea para uso público. C++ 0x puede mejorar en eso, pero eso no es relevante hoy. –

+0

@Tony, generalmente estoy de acuerdo, pero no es inusual definirlo explícitamente para fines de documentación. Además, al usar Doxygen u otro generador automático de documentación, un comentario generalmente no lo corta. Además, si lo define explícitamente, también lo probará en unidades, lo que detectaría roturas si, por ejemplo, se agregara un campo de puntero. –

1

Si tiene algunos recursos en su clase que se supone que debe administrar, entonces escribir su propio operador de asignación tiene sentido, sino confiar en el generado por el compilador.

+0

Sin recursos de ningún tipo. Sin cadenas tampoco – ExpatEgghead

8

La razón más común para definir explícitamente un operador de asignación es admitir "propiedad remota", básicamente, una clase que incluye uno o más punteros y posee los recursos a los que se refieren esos punteros. En tal caso, normalmente necesita definir la asignación, la copia (es decir, el constructor de copia) y la destrucción. Hay tres estrategias principales para estos casos (clasificados por la disminución de la frecuencia de uso):

  1. copia profunda
  2. referencia contando
  3. La transferencia de propiedad

copia profunda significa la asignación de un nuevo recurso para el objetivo de la asignación/copia. P.ej., una clase de cadena tiene un puntero al contenido de la cadena; cuando lo asigne, la asignación asigna un nuevo búfer para mantener el nuevo contenido en el destino y copia los datos del origen al búfer de destino. Esto se utiliza en la mayoría de las implementaciones actuales de varias clases estándar, como std::string y std::vector.

Recuento de referencias usado es bastante común también. Muchas (¿la mayoría?) Implementaciones más antiguas de std::string utilizan el recuento de referencias. En este caso, en lugar de asignar y copiar los datos para la cadena, simplemente incrementó un conteo de referencia para indicar el número de objetos de cadena que hacen referencia a un buffer de datos en particular. Solo asignó un nuevo búfer cuando/si el contenido de una cadena se modificó por lo que debe diferir de los demás (es decir, utilizó la copia en escritura). Sin embargo, con multiprocesamiento, debe sincronizar el acceso al recuento de referencias, que a menudo tiene un impacto grave en el rendimiento, por lo que en el código más reciente esto es bastante inusual (se usa principalmente cuando algo almacena así que mucha información que puede desperdiciar un poco del tiempo de CPU para evitar dicha copia).

La transferencia de propiedad es relativamente inusual. Es lo que se hace por std::auto_ptr. Cuando asigna o copia algo, la fuente de la asignación/copia se destruye básicamente: los datos se transfieren de uno a otro. Esto es (o puede ser) útil, pero la semántica es lo suficientemente diferente de la asignación normal que a menudo es contradictorio. Al mismo tiempo, en las circunstancias adecuadas, puede proporcionar una gran eficiencia y simplicidad. C++ 0x hará que la transferencia de propiedad sea considerablemente más manejable agregando un tipo unique_ptr que lo haga más explícito, y también agregue referencias rvalue, que facilitan la implementación de la transferencia de propiedad para una clase bastante grande de situaciones donde puede mejorar el rendimiento sin dando lugar a una semántica que sea visiblemente contradictoria.

Volviendo a la pregunta original, sin embargo, si no tiene propiedad remota para empezar, es decir, su clase no contiene punteros, es probable que no deba definir explícitamente un operador de asignación. (o dtor o copiar ctor). Un error del compilador que impidió el funcionamiento de los operadores de asignación definidos impidió pasar una gran cantidad de pruebas de regresión.

Incluso si de alguna manera se liberara, su defensa sería simplemente no usarlo. No hay espacio real para la pregunta de que tal lanzamiento sería reemplazado en cuestión de horas. Con un proyecto existente mediano a grande, no desea cambiar a un compilador hasta que haya estado en uso durante un tiempo en cualquier caso.

Cuestiones relacionadas