unidimensionales - suma de vectores en c++
¿Por qué C++ no admite el rango basado en bucles para arreglos dinámicos? (6)
¿Por qué C ++ no admite el rango basado en arrays dinámicos? Es decir, algo como esto:
int* array = new int[len];
for[] (int i : array) {};
Acabo de inventar la instrucción for[]
para rimar con new[]
y delete[]
. Según tengo entendido, el tiempo de ejecución tiene el tamaño de la matriz disponible (de lo contrario, delete[]
no podría funcionar), por lo que, en teoría, el rango basado en bucle también podría funcionar. ¿Cuál es la razón por la que no está hecho para trabajar?
¿Cuál es la razón por la que no está hecho para trabajar?
Un bucle basado en rango como
for(auto a : y) {
// ...
}
Es solo azúcar sintáctica para la siguiente expresión.
auto endit = std::end(y);
for(auto it = std::begin(y); it != endit; ++it) {
auto a = *it;
// ...
}
Como std::begin()
y std::end()
no se pueden usar con un puntero plano, esto no se puede aplicar con un puntero asignado con el new[]
.
Por lo que entiendo, el tiempo de ejecución tiene el tamaño de la matriz disponible (de lo contrario,
delete[]
no podría funcionar)
Cómo delete[]
realiza un seguimiento del bloque de memoria asignado con el new[]
(que no es necesariamente del mismo tamaño que el usuario especificó), es algo completamente diferente y el compilador probablemente ni siquiera sabe cómo hacerlo. exactamente esto se implementa.
int* array = new int[len]; for[] (int i : array) {}
Hay varios puntos que deben abordarse; Los abordaré uno a la vez.
¿El tiempo de ejecución sabe el tamaño de la matriz?
En ciertas condiciones, debe hacerlo. Como señaló, una llamada para delete[]
llamará al destructor de cada elemento (en orden de reserva) y, por lo tanto, debe saber cuántos hay.
Sin embargo, al no especificar que el número de elementos debe ser conocido y accesible, el estándar de C ++ permite que una implementación lo omita siempre que no se requiera la llamada al destructor ( std::is_trivially_destructible<T>::value
evalúa como true
) .
¿Puede el tiempo de ejecución distinguir entre puntero y matriz?
En general, no.
Cuando tienes un puntero, podría apuntar a cualquier cosa:
- un solo elemento, o un elemento en una matriz,
- el primer elemento en una matriz, o cualquier otro,
- una matriz en la pila, o una matriz en el montón,
- solo una matriz, o una parte de la matriz de un objeto más grande.
Esta es la razón por la que existe delete[]
, y usar delete
aquí sería incorrecto. Con delete[]
, el estado del usuario: este puntero apunta al primer elemento de una matriz asignada a un montón .
La implementación puede asumir que, por ejemplo, en los 8 bytes que preceden a este primer elemento, puede encontrar el tamaño de la matriz. Sin ti garantizando esto, esos 8 bytes podrían ser cualquier cosa.
Entonces, ¿por qué no ir hasta el final y crear
for[] (int i : array)
?
Hay dos razones:
- Como se mencionó, hoy una implementación puede elidir el tamaño de una serie de elementos; con esta nueva sintaxis
for[]
, ya no sería posible por tipo. - Que no vale la pena.
Seamos honestos, el new[]
y el delete[]
son reliquias de un tiempo anterior. Son increíblemente torpes:
- el número de elementos se debe conocer de antemano y no se puede cambiar,
- los elementos deben ser construibles por defecto, o de lo contrario C-ish,
y no es seguro de usar:
- el número de elementos es inaccesible para el usuario.
Generalmente no hay razón para usar new[]
y delete[]
en C ++ moderno. La mayoría de las veces debería preferirse un std::vector
; En los pocos casos en que la capacidad es superflua, un std::dynarray
es aún mejor (porque mantiene un seguimiento del tamaño).
Por lo tanto, sin una razón válida para seguir usando estas declaraciones, no hay ninguna motivación para incluir nuevas construcciones semánticas específicamente dedicadas a manejarlas.
Y si alguien tiene la motivación suficiente para hacer una propuesta de este tipo:
- la inhibición de la optimización actual, una violación de la filosofía de C ++ de "No pagas por lo que no usas", probablemente se llevaría a cabo contra ellos,
- la inclusión de una nueva sintaxis, cuando las propuestas modernas de C ++ han hecho todo lo posible para evitarla lo más posible (hasta el punto de tener una biblioteca definida
std::variant
), también se mantendría en su contra.
Recomiendo que simplemente use std::vector
.
Cuando tengas esto:
int* array = new int[len];
El problema aquí es que su variable llamada array
no es una matriz en absoluto. Es un puntero . Eso significa que solo contiene la dirección de un objeto (en este caso, el primer elemento de la matriz creada utilizando new
).
Para el rango basado en el trabajo, el compilador necesita dos direcciones, el principio y el final de la matriz.
Entonces el problema es que el compilador no tiene suficiente información para hacer esto:
// array is only a pointer and does not have enough information
for(int i : array)
{
}
Esto no está relacionado con matrices dinámicas, es más general. Por supuesto, para los arreglos dinámicos existe en algún lugar el tamaño para poder llamar a los destructores (pero recuerde que el estándar no dice nada sobre eso, solo que llamar a delete []
funciona según lo previsto).
El problema es que los punteros en general, dado que se le asigna un puntero, no se puede saber si corresponde a algún tipo de ... ¿qué?
Las matrices decaen a los punteros, pero dado un puntero, ¿qué puedes decir?
La razón es que, dado solo el valor de la array
punteros, el compilador (y su código) no tiene información sobre a qué apunta. Lo único que se sabe es que la array
tiene un valor que es la dirección de un solo int
.
Podría apuntar al primer elemento de una matriz asignada estáticamente. Podría apuntar a un elemento en medio de una matriz asignada dinámicamente. Podría apuntar a un miembro de una estructura de datos. Podría apuntar a un elemento de una matriz que está dentro de una estructura de datos. La lista continua.
Su código hará ASUNCIONES sobre lo que apunta el puntero. Se puede asumir que es una matriz de 50 elementos. Su código puede acceder al valor de len
y asumir puntos de array
en el (primer elemento de) una matriz de elementos len
. Si su código lo hace bien, todo funciona según lo previsto. Si su código se equivoca (por ejemplo, al acceder al elemento 50 de una matriz con 5 elementos), el comportamiento es simplemente indefinido. No está definido porque las posibilidades son infinitas: la contabilidad para hacer un seguimiento de lo que apunta REALMENTE un puntero arbitrario (más allá de la información de que hay una int
en esa dirección) sería enorme.
Estás comenzando con la ASUNCIÓN de que la array
apunta al resultado de new int[len]
. Pero esa información no se almacena en el valor de la array
, por lo que el compilador no tiene forma de volver a trabajar con un valor de len
. Eso sería necesario para que su enfoque "basado en el rango" funcione.
Mientras que, sí, dado array = new int[len]
, la maquinaria invocada por delete [] array
resolverá que la array
tiene elementos len
y los libera. Pero la delete [] array
también tiene un comportamiento indefinido si la array
resulta de algo distinto a una new []
expresión new []
. Incluso
int *array = new int;
delete [] array;
Da un comportamiento indefinido. No se requiere que el "tiempo de ejecución" funcione, en este caso, esa array
es en realidad la dirección de un único int
asignado dinámicamente (y no una matriz real). Por lo que no es necesario hacer frente a eso.
array
no es un array, sino un puntero y no hay información sobre el tamaño del "array". Por lo tanto, el compilador no puede deducir el begin
y el end
de esta matriz.
Vea la sintaxis del rango basado en bucle:
{
auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
range_expression : cualquier expresión que represente una secuencia adecuada (ya sea una matriz o un objeto para el cual se definen las funciones de miembro inicial y final o las funciones libres, ver más abajo) o una lista de iniciaciones preparadas.
auto
trabaja en tiempo de compilación. Por lo tanto, begin_expr
y end_expr
no se end_expr
en tiempo de ejecución.