La pregunta es: ¿cómo gestionan Array dinámicamente las matrices dinámicas cuando se configuran como miembros de la clase? ¿Se copian o se pasan por referencia? Delphi 10.3.3 utilizado.
El UpdateArraymétodo elimina el primer elemento de la matriz. Pero la longitud de la matriz permanece 2. El UpdateArrayWithParammétodo también elimina el primer elemento de la matriz. Pero la longitud de la matriz se reduce correctamente a 1.
Aquí hay un ejemplo de código:
interface
type
TSomeRec = record
Name: string;
end;
TSomeRecArray = array of TSomeRec;
TSomeRecUpdate = class
Arr: TSomeRecArray;
procedure UpdateArray;
procedure UpdateArrayWithParam(var ParamArray: TSomeRecArray);
end;
implementation
procedure TSomeRecUpdate.UpdateArray;
begin
Delete(Arr, 0, 1);
end;
procedure TSomeRecUpdate.UpdateArrayWithParam(var ParamArray: TSomeRecArray);
begin
Delete(ParamArray, 0, 1);
end;
procedure Test;
var r: TSomeRec;
lArr: TSomeRecArray;
recUpdate: TSomeRecUpdate;
begin
lArr := [];
r.Name := 'abc';
lArr := lArr + [r];
r.Name := 'def';
lArr := lArr + [r];
recUpdate := TSomeRecUpdate.Create;
recUpdate.Arr := lArr;
recUpdate.UpdateArray;
//(('def'), ('def')) <=== this is the result of copy watch value, WHY two values?
lArr := [];
r.Name := 'abc';
lArr := lArr + [r];
r.Name := 'def';
lArr := lArr + [r];
recUpdate.UpdateArrayWithParam(lArr);
//(('def')) <=== this is the result of copy watch value - WORKS
recUpdate.Free;
end;

Deleteprocedimiento. Tiene que reasignar la matriz dinámica, por lo que todos los punteros a ella "deben" moverse. Pero solo conoce uno de estos indicadores, a saber, el que le das.Respuestas:
¡Esta es una pregunta interesante!
Dado que
Deletecambia la longitud de la matriz dinámica , al igual que loSetLengthhace, tiene que reasignar la matriz dinámica. Y también cambia el puntero que se le asigna a esta nueva ubicación en la memoria. Pero obviamente no puede cambiar ningún otro puntero a la matriz dinámica anterior.Por lo tanto, debería disminuir el recuento de referencia de la matriz dinámica anterior y crear una nueva matriz dinámica con un recuento de referencia de 1. El puntero dado a
Deletese establecerá en esta nueva matriz dinámica.Por lo tanto, la matriz dinámica anterior debe estar intacta (a excepción de su recuento de referencia reducido, por supuesto). Esto está esencialmente documentado para la
SetLengthfunción similar :Pero sorprendentemente, esto no sucede en este caso.
Considere este ejemplo mínimo:
Elegí los valores para que sean fáciles de detectar en la memoria (Alt + Ctrl + E).
Después de (1),
aseñala$02A2C198en mi ejecución de prueba:Aquí el recuento de referencia es 2 y la longitud de la matriz es 2, como se esperaba. (Consulte la documentación del formato de datos interno para matrices dinámicas).
Después de (2),
a = b, es decir,Pointer(a) = Pointer(b). Ambos apuntan a la misma matriz dinámica, que ahora se ve así:Como se esperaba, el recuento de referencia ahora es 3.
Ahora, veamos qué sucede después de (3).
aahora apunta a una nueva matriz dinámica2A30F88en mi ejecución de prueba:Como se esperaba, esta nueva matriz dinámica tiene un recuento de referencia de 1 y solo el "elemento B".
Esperaría que la matriz dinámica anterior, que
btodavía apunta, se vea como antes pero con un recuento de referencia reducido de 2. Pero ahora se ve así:Aunque el recuento de referencia se reduce a 2, el primer elemento ha cambiado.
Mi conclusión es que
(1) Es parte del contrato del
Deleteprocedimiento que invalida todas las demás referencias a la matriz dinámica inicial.o
(2) Debería comportarse como describí anteriormente, en cuyo caso esto es un error.
Desafortunadamente, la documentación del
Deleteprocedimiento no menciona esto en absoluto.Se siente como un error.
Actualización: el código RTL
Eché un vistazo al código fuente del
Deleteprocedimiento, y esto es bastante interesante.Puede ser útil comparar el comportamiento con el de
SetLength(porque ese funciona correctamente):Si el recuento de referencia de la matriz dinámica es 1,
SetLengthintenta simplemente cambiar el tamaño del objeto de montón (y actualizar el campo de longitud de la matriz dinámica).De lo contrario,
SetLengthrealiza una nueva asignación de montón para una nueva matriz dinámica con un recuento de referencia de 1. El recuento de referencia de la matriz anterior se reduce en 1.De esta forma, se garantiza que el recuento de referencia final sea siempre
1, ya sea desde el principio o se ha creado una nueva matriz. (Es bueno que no siempre haga una nueva asignación de almacenamiento dinámico. Por ejemplo, si tiene una matriz grande con un recuento de referencia de 1, simplemente truncarla es más barato que copiarla en una nueva ubicación).Ahora, como
Deletesiempre hace que la matriz sea más pequeña, es tentador intentar simplemente reducir el tamaño del objeto de montón donde está. Y esto es de hecho lo que intenta el código RTLSystem._DynArrayDelete. Por lo tanto, en su caso,BBBBBBBBse mueve al principio de la matriz. Todo está bien.Pero luego llama
System.DynArraySetLength, que también es utilizado porSetLength. Y este procedimiento contiene el siguiente comentario,antes de detectar que el objeto está compartido (en nuestro caso, rec count = 3), realiza una nueva asignación de montón para una nueva matriz dinámica y copia la antigua (reducida) en esta nueva ubicación. Reduce el recuento de referencias de la matriz anterior y actualiza el recuento de referencias, la longitud y el puntero de argumento de la nueva.
Así que terminamos con una nueva matriz dinámica de todos modos. Pero los programadores RTL olvidaron que ya habían ensuciado la matriz original, que ahora consiste en la nueva matriz colocada encima de la anterior:
BBBBBBBB BBBBBBBB.fuente
Deleteen una matriz dinámica. Por un lado, no es barato en matrices grandes (ya que necesariamente necesita copiar una gran cantidad de datos). Y este problema actual me preocupa aún más, obviamente. Pero también esperemos y veamos si los otros miembros de Delphi de la comunidad SO están de acuerdo con mi análisis.SetLength,InsertyDeleteobviamente necesidad de reasignar. Simplemente cambiar un elemento (comob[2] := 4) afectará a cualquier otra variable de matriz dinámica que apunte a la misma matriz dinámica; No habrá copia.