usar sirven que punteros puntero para miembro los funciones funcion doble declaracion con como clase apuntadores c++ performance shallow-copy

sirven - ¿Puedo usar memcpy en C++ para copiar clases que no tienen punteros o funciones virtuales?



punteros c++ (10)

[...] pero ¿hay otros nasties ocultos que debería conocer?

Sí: su código hace ciertas suposiciones que no se sugieren ni documentan (a menos que usted las documente específicamente). Esta es una pesadilla para el mantenimiento.

Además, su implementación es básicamente piratería (si es necesario, no es algo malo) y puede depender (no estoy seguro de esto) de cómo su compilador actual implementa las cosas.

Esto significa que si actualiza el compilador / cadena de herramientas dentro de un año (o cinco) a partir de ahora (o simplemente cambia la configuración de optimización en su compilador actual) nadie recordará este truco (a menos que haga un gran esfuerzo para mantenerlo visible) y puede terminar con un comportamiento indefinido en sus manos, y los desarrolladores maldiciendo "quien hizo esto" unos años después.

No es que la decisión sea errónea, es que es (o será) inesperada para los mantenedores.

Para minimizar esto (¿inesperado?) Movería la clase a una estructura dentro de un espacio de nombres basado en el nombre actual de la clase, sin funciones internas en la estructura en absoluto. Entonces, dejas claro que estás viendo un bloque de memoria y tratándolo como un bloque de memoria.

En lugar de:

class MyClass { public: MyClass(); int a,b,c; double x,y,z; }; #define PageSize 1000000 MyClass Array1[PageSize],Array2[PageSize]; memcpy(Array1,Array2,PageSize*sizeof(MyClass));

Deberías:

namespace MyClass // obviously not a class, // name should be changed to something meaningfull { struct Data { int a,b,c; double x,y,z; }; static const size_t PageSize = 1000000; // use static const instead of #define void Copy(Data* a1, Data* a2, const size_t count) { memcpy( a1, a2, count * sizeof(Data) ); } // any other operations that you''d have declared within // MyClass should be put here } MyClass::Data Array1[MyClass::PageSize],Array2[MyClass::PageSize]; MyClass::Copy( Array1, Array2, MyClass::PageSize );

De esta manera usted:

  • deje en claro que MyClass :: Data es una estructura POD, no una clase (binarios serán iguales o muy parecidos, lo mismo si recuerdo bien), pero de esta manera también es visible para los programadores que leen el código.

  • centralice el uso de memcpy (si tiene que cambiar a std :: copy o algo más) en dos años, hágalo en un solo punto.

  • mantener el uso de memcpy cerca de la implementación de la estructura POD.

Digamos que tengo una clase, algo como lo siguiente;

class MyClass { public: MyClass(); int a,b,c; double x,y,z; }; #define PageSize 1000000 MyClass Array1[PageSize],Array2[PageSize];

Si mi clase no tiene punteros o métodos virtuales, ¿es seguro usar lo siguiente?

memcpy(Array1,Array2,PageSize*sizeof(MyClass));

La razón por la que lo pregunto es que estoy tratando con grandes colecciones de datos paginados, como se describe here , donde el rendimiento es crítico, y memcpy ofrece importantes ventajas de rendimiento sobre la asignación iterativa. Sospecho que debería estar bien, ya que el puntero ''this'' es un parámetro implícito en lugar de cualquier cosa almacenada, pero ¿hay otros nasties ocultos que debería conocer?

Editar:

Según los comentarios de sharptooths, los datos no incluyen ningún identificador o información de referencia similar.

Según el comentario de Paul R, he perfilado el código y, en este caso, evitar el constructor de copia es aproximadamente 4.5 veces más rápido. Parte de la razón aquí es que mi clase de matriz con plantilla es algo más compleja que el ejemplo simplista dado, y llama a una ubicación ''nueva'' cuando asigna memoria para tipos que no permiten la copia superficial. Esto significa efectivamente que se llama al constructor predeterminado así como al constructor de copia.

Segunda edición

Tal vez valga la pena señalar que acepto plenamente que el uso de memcpy de esta manera es una mala práctica y debe evitarse en los casos generales. El caso específico en el que se está utilizando es como parte de una clase de matriz con plantilla de alto rendimiento, que incluye un parámetro ''AllowShallowCopying'', que invocará memcpy en lugar de un constructor de copia. Esto tiene grandes implicaciones de rendimiento para operaciones como la eliminación de un elemento cerca del inicio de una matriz, y la localización de datos dentro y fuera del almacenamiento secundario. La mejor solución teórica sería convertir la clase en una estructura simple, pero dado que esto implica una gran cantidad de refactorización de una base de código grande, evitarla no es algo que quiera hacer.


Cuando hable sobre el caso al que se refiere, le sugiero que declare estructuras en lugar de clases . Hace que sea mucho más fácil de leer (y menos discutible :)) y el especificador de acceso predeterminado es público.

Por supuesto, puedes usar memcpy en este caso, pero ten en cuenta que no se recomienda agregar otros tipos de elementos en la estructura (como las clases de C ++) (debido a razones obvias, no sabes cómo los influenciará memcpy).


De acuerdo con la Norma, si el programador no proporciona un constructor de copia para una clase, el compilador sintetizará un constructor que muestre la inicialización por defecto de los miembros . (12.8.8) Sin embargo, en 12.8.1, el Estándar también dice:

Un objeto de clase se puede copiar de dos maneras, mediante la inicialización (12.1, 8.5), incluso para pasar el argumento de la función (5.2.2) y para el retorno del valor de la función (6.6.3), y por asignación (5.17). Conceptualmente, estas dos operaciones son implementadas por un constructor de copia (12.1) y un operador de asignación de copia (13.5.3).

La palabra operativa aquí es "conceptualmente", que, de acuerdo con Lippman da a los diseñadores del compilador una "salida" para que realicen la inicialización de miembros en "trivial" (12.8.6) constructores de copia definidos implícitamente.

En la práctica, entonces, los compiladores tienen que sintetizar constructores de copia para estas clases que exhiben un comportamiento como si estuvieran haciendo una inicialización de miembros. Pero si la clase exhibe "Bitwise Copy Semantics" (Lippman, p. 43), entonces el compilador no tiene que sintetizar un constructor de copia (lo que daría lugar a una llamada de función, posiblemente en línea) y, en su lugar, copiar en modo bit a bit. Este reclamo aparentemente está respaldado en el ARM , pero todavía no lo he buscado.

Usar un compilador para validar que algo cumple con la Norma es siempre una mala idea, pero compilar su código y ver el ensamblaje resultante parece verificar que el compilador no está realizando la inicialización de los miembros en un constructor de copia sintetizada, sino que hace un memcpy en memcpy lugar:

#include <cstdlib> class MyClass { public: MyClass(){}; int a,b,c; double x,y,z; }; int main() { MyClass c; MyClass d = c; return 0; }

El ensamblado generado para MyClass d = c; es:

000000013F441048 lea rdi,[d] 000000013F44104D lea rsi,[c] 000000013F441052 mov ecx,28h 000000013F441057 rep movs byte ptr [rdi],byte ptr [rsi]

... donde 28h es el sizeof(MyClass) .

Esto fue compilado bajo MSVC9 en modo de depuración.

EDITAR:

El largo y corto de este post es que:

1) Siempre y cuando la copia bit a bit muestre los mismos efectos secundarios que la copia de memberwise, el Estándar permite a los constructores de copias implícitas triviales hacer una memcpy lugar de copias de memberwise.

2) Algunos compiladores realmente hacen memcpy s en lugar de sintetizar un constructor de copia trivial que hace copias de miembros.


Funcionará, porque una clase (POD-) es lo mismo que una estructura (no completamente, acceso predeterminado ...), en C ++. Y puedes copiar una estructura POD con memcpy.

La definición de POD fue sin funciones virtuales, sin constructor, deconstructor sin herencia virtual ... etc.


Llamar a memcpy en clases que no son POD es un comportamiento indefinido. Sugiero seguir el consejo de Kirill para su afirmación. El uso de memcpy puede ser más rápido, pero si la operación de copia no es crítica para el rendimiento en su código, simplemente use la copia a nivel de bits.


Me daré cuenta de que admites que hay un problema aquí. Y usted es consciente de los posibles inconvenientes.

Mi pregunta es de mantenimiento. ¿Se siente seguro de que nadie incluirá un campo en esta clase que pueda dañar su gran optimización? Yo no, soy ingeniero, no profeta.

Entonces, en lugar de intentar mejorar la operación de copia ... ¿por qué no intentar evitarla por completo?

¿Sería posible cambiar la estructura de datos utilizada para el almacenamiento para detener el movimiento de elementos ... o al menos no tanto?

Por ejemplo, ¿conoces blist (el módulo Python)? B + Tree puede permitir el acceso al índice con rendimientos bastante similares a los vectores (un poco más lento, por cierto), por ejemplo, al tiempo que minimiza el número de elementos que se barajan al insertar / eliminar.

En lugar de ir rápido y sucio, ¿quizás debería centrarse en encontrar una mejor colección?


Permítame darle una respuesta empírica: en nuestra aplicación en tiempo real, hacemos esto todo el tiempo, y funciona bien. Este es el caso en MSVC para Wintel y PowerPC y GCC para Linux y Mac, incluso para las clases que tienen constructores.

No puedo citar el capítulo y el verso del estándar de C ++ para esto, solo evidencia experimental.


Podría usar memcpy para copiar matrices de tipos POD. Y será una buena idea agregar una boost::is_pod estática para boost::is_pod is true. Tu clase no es un tipo POD ahora.

Los tipos aritméticos, los tipos de enumeración, los tipos de punteros y los tipos de punteros a miembros son POD.

Una versión calificada para cv de un tipo POD es en sí misma un tipo POD.

Una matriz de POD es en sí POD. Una estructura o unión, todos cuyos miembros de datos no estáticos son POD, es POD si tiene:

  • No hay constructores declarados por el usuario.
  • No miembros privados o protegidos de datos no estáticos.
  • No hay clases de base.
  • No hay funciones virtuales.
  • No hay miembros de datos no estáticos de tipo de referencia.
  • Ningún operador de asignación de copia definido por el usuario.
  • Ningún destructor definido por el usuario.

Su clase tiene un constructor, por lo que no es POD en el sentido de que es una estructura C. Por lo tanto, no es seguro copiarlo con memcpy (). Si desea datos POD, elimine el constructor. Si desea datos que no sean POD, donde la construcción controlada es esencial, no use memcpy (), no puede tener ambos.


Usted podría Pero primero pregúntate:

¿Por qué no usar simplemente el constructor de copias que proporciona su compilador para hacer una copia de miembros?

¿Tiene problemas de rendimiento específicos para los que necesita optimizar?

La implementación actual contiene todos los tipos de POD: ¿qué sucede cuando alguien la cambia?