2010-08-09 22 views
73

entiendo no se admite que la asignación miembro por miembro de matrices, de manera que el siguiente no funcionará:¿Por qué C++ admite la asignación de conjuntos de matrices en estructuras, pero no en general?

int num1[3] = {1,2,3}; 
int num2[3]; 
num2 = num1; // "error: invalid array assignment" 

que acaba de ser aceptado esto como hecho, pensando que el objetivo de la lengua es proporcionar un marco abierto y deje que el usuario decida cómo implementar algo como la copia de una matriz.

Sin embargo, el siguiente no trabajo:

struct myStruct {int num[3];}; 
myStruct struct1={{1,2,3}}; 
myStruct struct2; 
struct2 = struct1; 

La matriz num[3] es miembro-sabia asignado de su instancia en struct1, en su caso en struct2.

¿Por qué la asignación de arreglos para miembros es compatible con las estructuras, pero no en general?

edición: Roger comentario Pate 's en el hilo std::string in struct - Copy/assignment issues? parece apuntar en la dirección general de la respuesta, pero no sé lo suficiente como para confirmar que yo mismo.

editar 2: Muchas respuestas excelentes. Elijo Luther Blissett porque me preguntaba sobre el fundamento filosófico o histórico detrás del comportamiento, pero James McNellis es útil también en referencia a la documentación de especificaciones relacionada.

+4

Estoy haciendo que esto tenga tanto C como C++ como etiquetas, porque esto se origina de C. Además, buena pregunta. – GManNickG

+3

Puede valer la pena señalar que hace mucho tiempo en C, la asignación de estructura no era posible en general y que tenía que usar 'memcpy()' o similar. – ggg

+0

¡Buena pregunta! –

Respuesta

36

Aquí está mi opinión sobre ella:

El desarrollo del lenguaje C ofrece una idea de la evolución del tipo de matriz en C:

I' Trataremos de delinear lo del arreglo:

Los precursores B de C y BCPL no tenían di stinct tipo de matriz, una declaración como:

auto V[10] (B) 
or 
let V = vec 10 (BCPL) 

declararían V sea un puntero (sin tipo) que se inicializa para que apunte a una región no utilizada de 10 "palabras" de la memoria.B ya usó * para la desreferenciación del puntero y tenía la notación abreviada [], *(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 cierta memoria. Esto causó problemas cuando Dennis Ritchie intentó extender B con tipos de estructuras. Quería matrices para formar parte de las estructuras, como en C Hoy:

struct { 
    int inumber; 
    char name[14]; 
}; 

Pero con el B, el concepto BCPL de matrices como punteros, esto habría requerido el campo name para contener un puntero, que tenía 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 las matrices "que se convierten en puntero si las mira" regla. Es un hack de compatiblity, que resultó ser muy útil, porque permitía matrices de tamaño abierto, etc.

Y aquí está mi suposición de por qué no se puede asignar una matriz: como las matrices eran punteros en B, simplemente podías 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 se permitió, lo que ayudó a detectar los pocos programas que hicieron este 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 punteros 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 espurias (¿es una asignación ba = puntero o una copia de elementos?) Y otros problemas (¿cómo se pasa una matriz por valor?) Sin resolver un problema. Simplemente haga todo ¡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 añadió asignación struct (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 en K & R C temprano. Debes copiarlos a nivel de miembro con memcpy y solo puedes pasarles 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.

+2

¡Suena muy razonable! – Philipp

+0

Es una pena C no definió una sintaxis, p. Ej. 'int [10] c;' para hacer que lvalue 'c' se comporte como una matriz de diez elementos, en lugar de como un puntero al primer elemento de una matriz de diez elementos. Hay algunas situaciones en las que es útil poder crear un typedef que asigna espacio cuando se usa para una variable, pero pasa un puntero cuando se usa como argumento de función, pero la incapacidad de tener un valor de tipo de matriz es una debilidad semántica significativa. en el lenguaje – supercat

2

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 matrices son que

  • una matriz no conoce su propio tamaño
  • el nombre de una array se convierte en un puntero a su primer elemento a la menor provocación

Y creo que esta es la diferencia fundamental tween 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.

Por lo tanto, el compilador no puede distinguir entre int a [10] e int b [20].

Las estructuras, sin embargo, no tienen la misma ambigüedad.

+3

Esa página habla acerca de pasar arreglos a funciones (que no se puede hacer, por lo que es solo un puntero, que es lo que quiere decir cuando dice que pierde su tamaño). Eso no tiene nada que ver con la asignación de matrices a las matrices. Y no, una variable de matriz no es solo "realmente" un puntero al primer elemento, sino una matriz. Las matrices no son punteros. – GManNickG

+0

Gracias por el comentario, pero cuando leí esa sección del artículo, él dice por adelantado que las matrices no conocen su propio tamaño, luego utiliza un ejemplo donde las matrices se pasan como argumentos para ilustrar ese hecho. Entonces, cuando las matrices son pasadas como argumentos, ¿perdieron la información sobre su tamaño, o nunca tuvieron la información para empezar? Yo asumí lo último. –

+2

El compilador puede distinguir entre dos matrices de diferentes tamaños: intente imprimir 'sizeof (a)' contra 'sizeof (b)' o pase 'a' a' void f (int (&) [20]); ' . –

23

En cuanto 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 lvalue modificables como su operando de la izquierda

Una matriz no es un lvalue modificable.

Sin embargo, la asignación a un objeto de tipo de clase se define especialmente (§5.17/4):

La asignación a los objetos de una clase se define mediante el operador de asignación de copias.

Por lo tanto, esperamos que ver lo que hace el operador de asignación de copia declarado implícitamente para una clase (§12.8/13):

El operador de asignación de copia definido implícitamente para la clase X realiza la asignación miembro por miembro de sus subobjetos. ... Cada subobjeto se asigna de la manera apropiada a su tipo:
...
- si el subobjeto es una matriz, se asigna cada elemento, de la manera apropiada para el tipo de elemento
...

Por lo tanto, 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á una modi fi lvalue capaz como su operando de la izquierda.

Y §6.3.2.1/1:

Una modi fi cable lvalue es un valor-i que no tiene tipo de matriz ... [otras limitaciones siguen]

En C, misiones es mucho más simple que en C++ (§6.5.16.1/2):

en asignación simple (=), el valor del operando derecho se convierte en el tipo de la expresión de asignación y reemplaza el valor almacenado en el objeto designado por el operando de la izquierda.

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.

+1

¿Por qué las matrices son inmutables? O más bien, ¿por qué no se define la asignación especialmente para matrices como cuando está en un tipo de clase? – GManNickG

+1

@GMan: Esa es la pregunta más interesante, ¿verdad? Para C++ la respuesta es probablemente "porque así es como está en C", y para C, supongo que es solo por cómo evolucionó el lenguaje (es decir, el motivo es histórico, no técnico), pero no estaba vivo cuando la mayor parte de eso tuvo lugar, entonces dejaré que alguien más conocedor responda esa parte :-P (FWIW, no puedo encontrar nada en los documentos de justificación C90 o C99). –

+1

¿Alguien sabe dónde está la definición de "valor l modificable" en el estándar C++ 03? Debería estar en §3.10. El índice dice que está definido en esa página, pero no lo es. La nota (no normativa) en §8.3.4/5 dice "Los objetos de los tipos de matriz no pueden ser modificados, ver 3.10," pero §3.10 no usa una vez la palabra "matriz". –

0

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 que no está permitido.

y, por supuesto, struct2 = struct1;

Aquí, el objeto struct1 se asigna a otro objeto.

+0

Y la asignación de estructuras eventualmente asignará el miembro de la matriz, lo que implica exactamente la misma pregunta. ¿Por qué se permite uno y no el otro cuando se trata de una matriz en ambas situaciones? – GManNickG

+1

De acuerdo. Pero el primero es impedido por el compilador (num2 = num1).El segundo no es impedido por el compilador. Eso hace una gran diferencia. – user373215

+0

Si las matrices fueran asignables, 'num2 = num1' se comportaría perfectamente bien. Los elementos de 'num2' tendrían el mismo valor del elemento correspondiente de' num1'. – juanchopanza

Cuestiones relacionadas