¿Por qué C++ admite la asignación de arreglos en forma de miembro dentro de las estructuras, pero no en general?
arrays variable-assignment (4)
Aquí está mi opinión sobre esto:
El desarrollo del lenguaje C ofrece algunas ideas sobre la evolución del tipo de matriz en C:
Trataré de delinear lo de la matriz:
Los precursores B de C y BCPL no tenían un tipo de matriz distinta, una declaración como:
auto V[10] (B)
or
let V = vec 10 (BCPL)
declararía que V es un puntero (sin tipo) que se inicializa para apuntar a una región no utilizada de 10 "palabras" de memoria. B ya se usó *
para la desreferenciación del puntero y tenía la notación de mano corta []
, *(V+i)
significaba V[i]
, al igual que en C / C ++ en la actualidad. Sin embargo, V
no es una matriz, sigue siendo un puntero que tiene que apuntar a algo de memoria. Esto causó problemas cuando Dennis Ritchie intentó extender B con tipos de estructuras. Quería que las matrices formen parte de las estructuras, como en C hoy:
struct {
int inumber;
char name[14];
};
Pero con el concepto B, BCPL de arreglos como punteros, esto habría requerido que el campo de name
contenga un puntero que tuvo que ser inicializado en el tiempo de ejecución a una región de memoria de 14 bytes dentro de la estructura. El problema de inicialización / disposición se resolvió finalmente dando a las matrices un tratamiento especial: el compilador rastrearía la ubicación de las matrices en las estructuras, en la pila, etc. sin requerir realmente que el puntero a los datos se materialice, excepto en expresiones que involucren las matrices. Este tratamiento permitió que casi todo el código B aún se ejecutara y es la fuente de la regla "las matrices se convierten en puntero si las miras" . Es un hack de compatiblity, que resultó ser muy útil, ya que permitía matrices de tamaño abierto, etc.
Y aquí está mi suposición de por qué la matriz no puede asignarse: como las matrices eran punteros en B, simplemente podría escribir:
auto V[10];
V=V+5;
para volver a establecer una "matriz". Esto ahora no tenía sentido, porque la base de una variable de matriz ya no era un valor l. Por lo tanto, esta asignación no fue permitida, lo que ayudó a detectar los pocos programas que hicieron esta rebase en las matrices declaradas . Y luego, esta noción se atascó: como las matrices nunca fueron diseñadas para ser citestizadas de primera clase del sistema tipo C, fueron tratadas principalmente como bestias especiales que se convierten en puntero si las usas. Y desde cierto punto de vista (que ignora que los C-arrays son un hack fallido), no tener en cuenta la asignación de matrices aún tiene sentido: una matriz abierta o un parámetro de función de matriz se trata como un puntero sin información de tamaño. El compilador no tiene la información para generar una asignación de matriz para ellos y se requirió la asignación del puntero por razones de compatibilidad. La introducción de la asignación de matriz para las matrices declaradas habría introducido errores a través de asignaciones espúreas (¿es una asignación ba-puntero o una copia de elementos?) Y otros problemas (¿cómo se pasa una matriz por valor?) Sin resolver realmente un problema. ¡explícito con memcpy!
/* Example how array assignment void make things even weirder in C/C++,
if we don''t want to break existing code.
It''s actually better to leave things as they are...
*/
typedef int vec[3];
void f(vec a, vec b)
{
vec x,y;
a=b; // pointer assignment
x=y; // NEW! element-wise assignment
a=x; // pointer assignment
x=a; // NEW! element-wise assignment
}
Esto no cambió cuando una revisión de C en 1978 agregó la asignación de estructura ( http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf ). A pesar de que los registros eran tipos distintos en C, no fue posible asignarlos a principios de K & R C. Tuvieron que copiarlos a nivel de miembro con memcpy y solo se les podían pasar punteros como parámetros de función. La asignación (y el paso de parámetros) ahora simplemente se definía como la memcpy de la memoria en bruto de la estructura y, como esto no podía romper el código existente, se agregó fácilmente. Como efecto colateral involuntario, esto introdujo implícitamente algún tipo de asignación de matriz, pero esto sucedió en algún lugar dentro de una estructura, por lo que esto realmente no podría introducir problemas con la forma en que se utilizaron las matrices.
Entiendo que no se admite la asignación de matrices en forma de miembro, por lo que lo siguiente no funcionará:
int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"
Simplemente acepté esto como un hecho, pensando que el objetivo del lenguaje es proporcionar un marco abierto, y dejar que el usuario decida cómo implementar algo como la copia de una matriz.
Sin embargo, lo siguiente funciona:
struct myStruct {int num[3];};
myStruct struct1={{1,2,3}};
myStruct struct2;
struct2 = struct1;
La matriz num[3]
se asigna a nivel de miembro desde su instancia en struct1
, en su instancia en struct2
.
¿Por qué la asignación de matrices compatible con miembros para las estructuras, pero no en general?
editar : el comentario de Roger Pate en el hilo std :: string en struct - ¿Problemas de copia / asignación? parece apuntar en la dirección general de la respuesta, pero no sé lo suficiente como para confirmarlo yo mismo.
edición 2 : muchas respuestas excelentes. Elijo Luther Blissett porque me preguntaba sobre el fundamento filosófico o histórico del comportamiento, pero la referencia de James McNellis a la documentación relacionada con las especificaciones también fue útil.
Con respecto a los operadores de asignación, el estándar de C ++ dice lo siguiente (C ++ 03 §5.17 / 1):
Hay varios operadores de asignación ... todos requieren un valor l modificable como su operando izquierdo
Una matriz no es un valor l modificable.
Sin embargo, la asignación a un objeto de tipo de clase se define especialmente (§5.17 / 4):
La asignación a objetos de una clase está definida por el operador de asignación de copias.
Entonces, buscamos ver qué hace el operador de asignación de copias implícitamente declarado para una clase (§12.8 / 13):
El operador de asignación de copia implícitamente definido para la clase X realiza la asignación de sus objetos subobjetos en forma de miembro. ... Cada subobjeto se asigna de la manera adecuada a su tipo:
...
- si el subobjeto es una matriz, cada elemento se asigna, de la manera apropiada para el tipo de elemento
...
Entonces, para un objeto de tipo de clase, las matrices se copian correctamente. Tenga en cuenta que si proporciona un operador de asignación de copias declarado por el usuario, no puede aprovecharlo y deberá copiar la matriz elemento por elemento.
El razonamiento es similar en C (C99 §6.5.16 / 2):
Un operador de asignación tendrá un valor l modi fi cable como su operando izquierdo.
Y §6.3.2.1 / 1:
Un lvalue modi fi cable es un lvalue que no tiene tipo de matriz ... [siguen otras restricciones]
En C, la asignación es mucho más simple que en C ++ (§6.5.16.1 / 2):
En la asignación simple (=), el valor del operando derecho se convierte al tipo de expresión de asignación y reemplaza el valor almacenado en el objeto designado por el operando izquierdo.
Para la asignación de objetos tipo struct, los operandos izquierdo y derecho deben tener el mismo tipo, por lo que el valor del operando derecho simplemente se copia en el operando izquierdo.
En este enlace: http://www2.research.att.com/~bs/bs_faq2.html hay una sección sobre la asignación de matrices:
Los dos problemas fundamentales con las matrices son que
- una matriz no sabe su propio tamaño
- el nombre de una matriz se convierte en un puntero a su primer elemento a la menor provocación
Y creo que esta es la diferencia fundamental entre arrays y structs. Una variable de matriz es un elemento de datos de bajo nivel con conocimiento propio limitado. Fundamentalmente, es un pedazo de memoria y una forma de indexarlo.
Entonces, el compilador no puede distinguir entre int a [10] e int b [20].
Las estructuras, sin embargo, no tienen la misma ambigüedad.
Lo sé, todos los que respondieron son expertos en C / C ++. Pero pensé, esta es la razón principal.
num2 = num1;
Aquí está tratando de cambiar la dirección base de la matriz, lo cual no es permisible.
y, por supuesto, struct2 = struct1;
Aquí, el objeto struct1 se asigna a otro objeto.