c++ c++11 copy-constructor language-lawyer memcpy

c++ - ¿Es memcpy una construcción o asignación de tipo de copia trivial?



c++11 copy-constructor (2)

En el mismo borrador, también encontrará el siguiente texto, directamente después del texto que citó:

Para cualquier tipo T trivialmente copiable, si dos punteros a T apuntan a objetos T obj1 y obj2 , donde ni obj1 ni obj2 es un subobjeto de clase base, si los bytes subyacentes (1.7) que forman el obj1 se copian en obj2 , obj2 posteriormente mantenga el mismo valor que obj1 .

Tenga en cuenta que esto habla de un cambio en el valor de obj2 , no de destruir el objeto obj2 y crear un nuevo objeto en su lugar. Dado que no se cambia el objeto, sino que solo se modifica su valor, todos los punteros o referencias a sus miembros deben seguir siendo válidos.

Digamos que tiene un objeto de tipo T y un búfer de memoria adecuadamente alineado alignas(T) unsigned char[sizeof(T)] . Si usa std::memcpy para copiar desde el objeto de tipo T a la matriz de caracteres unsigned char , ¿se considera eso como construcción de copia o asignación de copia?

Si un tipo es trivialmente copiable pero no de diseño estándar, es concebible que una clase como esta:

struct Meow { int x; protected: // different access-specifier means not standard-layout int y; };

podría implementarse así, porque el compilador no está obligado a usar el diseño estándar:

struct Meow_internal { private: ptrdiff_t x_offset; ptrdiff_t y_offset; unsigned char buffer[sizeof(int) * 2 + ANY_CONSTANT]; };

El compilador podría almacenar y de Meow dentro del búfer en cualquier parte del buffer , posiblemente incluso en un desplazamiento aleatorio dentro del buffer , siempre que estén alineados correctamente y no se superpongan. El desplazamiento de y puede incluso variar aleatoriamente con cada construcción si el compilador lo desea. ( x podría ir después de y si el compilador lo desea porque la Norma solo requiere que los miembros del mismo especificador de acceso estén en orden, y y tienen diferentes especificadores de acceso).

Esto cumpliría con los requisitos de ser trivialmente copiable; un memcpy copiaría los campos de desplazamiento ocultos, por lo que la nueva copia funcionaría. Pero algunas cosas no funcionarían. Por ejemplo, mantener un puntero a x en un memcpy se rompería:

Meow a; a.x = 2; a.y = 4; int *px = &a.x; Meow b; b.x = 3; b.y = 9; std::memcpy(&a, &b, sizeof(a)); ++*px; // kaboom

Sin embargo, ¿está realmente permitido el compilador implementar una clase de copia trivial de esta manera? La desreferenciación de px solo debe ser un comportamiento indefinido si la vida útil del ax ha finalizado. Lo tiene? Las partes relevantes del proyecto de norma N3797 no son muy claras sobre el tema. Esta es la sección [basic.life] / 1 :

La vida útil de un objeto es una propiedad de tiempo de ejecución del objeto. Se dice que un objeto tiene una inicialización no trivial si es de una clase o tipo agregado y uno de sus miembros es iniciado por un constructor diferente al de un constructor predeterminado trivial. [ Nota: la inicialización por un constructor trivial de copiar / mover no es una inicialización trivial. - nota final ] La vida útil de un objeto de tipo T comienza cuando:

  • se obtiene almacenamiento con la alineación y tamaño adecuados para el tipo T , y
  • Si el objeto tiene una inicialización no trivial, su inicialización está completa.

La vida útil de un objeto de tipo T finaliza cuando:

  • si T es un tipo de clase con un destructor no trivial ( [class.dtor] ), se inicia la llamada del destructor, o
  • El almacenamiento que ocupa el objeto se reutiliza o libera.

Y esto es [tipos de base] / 3 :

Para cualquier objeto (que no sea un subobjeto de clase base) de tipo T pueda copiar de forma trivial, ya sea que el objeto tenga o no un valor válido de tipo T , los bytes subyacentes ( [intro.memory] ) que forman el objeto se pueden copiar en una Arreglo de char o unsigned char . Si el contenido de la matriz de char o unsigned char se copia nuevamente en el objeto, el objeto mantendrá su valor original. ejemplo omitido

La pregunta entonces es: ¿es una sobrescritura memcpy de una instancia de clase que se puede copiar de forma trivial "construcción de copia" o "asignación de copia"? La respuesta a la pregunta parece decidir si Meow_internal es una forma válida para que un compilador implemente Meow clase de copia trivial.

Si memcpy es "construcción de copia", entonces la respuesta es que Meow_internal es válido, porque la construcción de copia está reutilizando la memoria. Si memcpy es "copia-asignación", entonces la respuesta es que Meow_internal no es una implementación válida, porque la asignación no invalida los punteros a los miembros instanciados de una clase. Si memcpy es ambas cosas, no tengo idea de cuál es la respuesta.


Para mí es claro que el uso de std::memcpy produce ni construcción ni asignación. No es construcción, ya que no se llamará constructor. Tampoco es asignación, ya que el operador de asignación no será llamado. Dado que un objeto que se puede copiar trivialmente tiene destructores triviales, constructores (copiar / mover) y operadores de asignación (copiar / mover), el punto es bastante discutible.

Parece que has citado el ¶2 de §3.9 [tipos de base]. En el ¶3, dice:

Para cualquier tipo de T trivialmente copiable, si dos punteros a T apuntan a objetos T obj1 y obj2 , donde ni obj1 ni obj2 es un subobjeto de clase base, si los bytes subyacentes (1.7) que forman el obj1 se copian en obj2 , 41 obj2 posteriormente mantendrá el mismo valor que obj1 . [Ejemplo:
T* t1p;
T* t2p;
// siempre que t2p apunte a un objeto inicializado ...
std::memcpy(t1p, t2p, sizeof(T));
// en este punto, cada subobjeto de tipo trivialmente copiable en *t1p contiene
// el mismo valor que el subobjeto correspondiente en *t2p
- ejemplo final]
41) Al usar, por ejemplo, las funciones de biblioteca (17.6.1.2) std::memcpy o std::memmove .

Claramente, el estándar destinado a permitir que *t1p sea ​​utilizable en todas las formas *t2p sería.

Continuando con el ¶4:

La representación de objeto de un objeto de tipo T es la secuencia de N objetos de caracteres sin signo tomados por el objeto de tipo T , donde N es igual a sizeof(T) . La representación del valor de un objeto es el conjunto de bits que contienen el valor de tipo T Para tipos trivialmente copiables, la representación del valor es un conjunto de bits en la representación del objeto que determina un valor, que es un elemento discreto de un conjunto de valores definido por la implementación. 42
42) La intención es que el modelo de memoria de C ++ sea compatible con el del lenguaje de programación C ISO / IEC 9899.

El uso de la palabra frente a ambos términos definidos implica que cualquier tipo dado solo tiene una representación de objeto y un objeto dado tiene solo una representación de valor. Su hipotético tipo interno no debería existir. La nota al pie deja en claro que la intención es que los tipos copiables trivialmente tengan un diseño de memoria compatible con C. La expectativa es que incluso un objeto con un diseño no estándar, al copiarlo alrededor, seguirá siendo útil.