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 aT
apuntan a objetosT
obj1
yobj2
, donde niobj1
niobj2
es un subobjeto de clase base, si los bytes subyacentes (1.7) que forman elobj1
se copian enobj2
,obj2
posteriormente mantenga el mismo valor queobj1
.
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 tipoT
, los bytes subyacentes ( [intro.memory] ) que forman el objeto se pueden copiar en una Arreglo dechar
ounsigned char
. Si el contenido de la matriz dechar
ounsigned 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 aT
apuntan a objetosT
obj1
yobj2
, donde niobj1
niobj2
es un subobjeto de clase base, si los bytes subyacentes (1.7) que forman elobj1
se copian enobj2
, 41obj2
posteriormente mantendrá el mismo valor queobj1
. [Ejemplo:
T* t1p;
T* t2p;
// siempre quet2p
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
ostd::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 tipoT
, donde N es igual asizeof(T)
. La representación del valor de un objeto es el conjunto de bits que contienen el valor de tipoT
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.