2010-08-05 11 views
8

Miré un poco las matrices dinámicas en D2, y las encontré muy difíciles de entender. También parece que estoy interpretando la especificación erróneamente ... Trabajar en una referencia o porción de una matriz dinámica parece muy propenso a errores cuando se cambian las matrices ... ¿O simplemente no entiendo los fundamentos?¿Es una mala práctica alterar las matrices dinámicas que tienen referencias sobre ellas?

Refiriéndose a la misma matriz sólo comparte los artículos reales:

auto a = [1]; 
auto b = a; 
assert(&a != &b); // different instance; Doesn't share length 
assert(a.ptr == b.ptr); // same items 
assert(a == [1]); 
assert(a == b); 

Como hacen referencia a la misma matriz, el cambio de uno cambia el otro:

auto a = [1,2]; 
auto b = a; 
a[1] = 20; 
assert(a == [1,20]); 
assert(a == b); 

De la especificación en la matriz de

Para maximizar la eficiencia, el tiempo de ejecución siempre intenta cambiar el tamaño de la matriz para evitar extra copiando Siempre hará una copia si el nuevo tamaño es más grande y la matriz no fue asignada a través del operador nuevo o una operación de cambio de tamaño anterior .

Así cambiando la longitud no se rompe neccesarily la referencia:

auto a = [1]; 
auto b = a; 
b.length = 2; 
assert(b == [1,0]); 
assert(a == [1]); // a unchanged even if it refers to the same instance 
assert(a.ptr == b.ptr); // but still the same instance 

// So updates to one works on the other 
a[0] = 10; 
assert(a == [10]); 
assert(b == [10,0]); 

De la especificación en la matriz de

concatenación siempre crea una copia de sus operandos, incluso si una de las operandos es un conjunto 0 longitud

auto a = [1]; 
auto b = a; 
b ~= 2; // Should make a copy, right..? 
assert(a == [1]); 
assert(b == [1,2]); 
assert(a != b); 
assert(a4.ptr == b.ptr); // But it's still the same instance 
a[0] = 10; 
assert(b == [10,2]); // So changes to a changes b 

Pero cuando las matrices se paso el uno del otro, los valores se copian a una nueva ubicación y la referencia roto:

auto a = [1]; 
auto b = a; 
b ~= 2; 
assert(a == [1]); 
assert(b == [1,2]); 

a.length = 2; // Copies values to new memory location to not overwrite b's changes 
assert(a.ptr != b.ptr); 

Cambio de la longitud de ambas matrices antes de hacer un cambio da el mismo resultado que el anterior (que se lo esperar que esto da la anterior):

auto a = [1]; 
auto b = a; 
a.length = 2; 
b.length = 2; 
a[1] = 2; 
assert(a == [1,2]); 
assert(b == [1,0]); 
assert(a.ptr != b.ptr); 

Y la misma longitud o al cambiar cancatenating (I esperaría que este le dio el anterior):

auto a = [1]; 
auto b = a; 
b.length = 2; 
a ~= 2; 
assert(a == [1,2]); 
assert(b == [1,0]); 
assert(a.ptr != b.ptr); 

Pero luego las rebanadas también entran en escena, ¡y de repente es aún más complicado! Las rebanadas podrían quedar huérfanos ...

auto a = [1,2,3]; 
auto b = a; 
auto slice = a[1..$]; // [2,3]; 
slice[0] = 20; 
assert(a == [1,20,3]); 
assert(a == b); 

a.length = 4; 
assert(a == [1,20,3,0]); 
slice[0] = 200; 
assert(b == [1,200,3]); // the reference to b is still valid. 
assert(a == [1, 20, 3, 0]); // but the reference to a is now invalid.. 

b ~= 4; 
// Now both references is invalid and the slice is orphan... 
// What does the slice modify? 
assert(a.ptr != b.ptr); 
slice[0] = 2000; 
assert(slice == [2000,3]); 
assert(a == [1,20,3,0]); 
assert(b == [1,200,3,4]); 

Así que ... ¿Es una mala práctica de tener múltiples referencias a la misma matriz dinámica? ¿Y pasando rodajas, etc.? ¿O estoy fuera de aquí, perdiendo todo el punto de las matrices dinámicas en D?

Respuesta

10

En general, parece que entiende las cosas bastante bien, pero parece que está malinterpretando el propósito de la propiedad ptr. Sí no indica si dos matrices se refieren a la misma instancia. Lo que hace es llevarte al puntero de lo que efectivamente es la matriz C debajo. Una matriz en D tiene su length como parte de ella, por lo que se parece más a una estructura con una longitud y un puntero a una matriz C que a una matriz en C. ptr le permite obtener la matriz C y pasarla al código C o C++.Probablemente no deberías usarlo para nada en código D puro. Si desea probar si dos variables de matriz se refieren a la misma instancia, a continuación, se utiliza el operador is (o !is permite comprobar que son diferentes instancias):

assert(a is b); //checks that they're the same instance 
assert(a !is b); //checks that they're *not* the same instance 

Todo lo que ptr en igualdad de condiciones para los dos conjuntos haría indicar es que su primer elemento está en el mismo lugar en la memoria. En particular, sus length s pueden diferir. Sin embargo, significa que cualquier elemento superpuesto se verá alterado en ambas matrices si las modificas en una de ellas.

Al cambiar el length de una matriz, D intenta evitar la reasignación, pero podría decidir reasignar, por lo que no puede confiar necesariamente en si se reasignaría o no. Por ejemplo, va a reasignar si no hacerlo pisará la memoria de otra matriz (incluidos los que tienen el mismo valor para ptr). También podría reasignar si no hay memoria suficiente para cambiar su tamaño en su lugar. Básicamente, se reasignará si no hacerlo pisará la memoria de otra matriz, y puede reasignar o no lo contrario. Por lo tanto, generalmente no es una buena idea confiar en si una matriz reasignará o no cuando establezca su length.

Hubiera esperado anexar para copiar siempre según los documentos, pero según las pruebas, parece que funciona igual que length (no sé si eso significa que los documentos deben actualizarse o si es un error - supongo que los documentos deben actualizarse). En cualquier caso, ciertamente no puede confiar en otras referencias a esa matriz para seguir haciendo referencia a la misma matriz después de agregarla.

En cuanto a las rebanadas, funcionan como se esperaba y se usan mucho en D, especialmente en la biblioteca estándar, Phobos. Una porción es un rango para una matriz y los rangos son un concepto central en Phobos. Sin embargo, al igual que muchos otros rangos, alterar el contenedor para el que el rango/corte podría invalidar ese rango/porción. Es por eso que cuando usa funciones que pueden cambiar el tamaño de los contenedores en Phobos, debe usar las funciones antes mencionadas con estable (por ejemplo, stableRemove() o stableInsert()) si no desea arriesgarse a invalidar los rangos que tiene para ese contenedor.

Además, un corte es una matriz al igual que la matriz que apunta. Entonces, naturalmente, alterar su length o agregarlo va a seguir todas las mismas reglas que aquellas para alterar el length o agregarse a cualquier otro arreglo, y por lo tanto podría ser reasignado y dejar de ser un corte en otro arreglo.

Bastante, solo debe tener en cuenta que alterar el length de una matriz de alguna manera podría dar como resultado una reasignación, por lo que debe evitar hacerlo si desea que las referencias sigan refiriéndose a la misma instancia de matriz. Y si necesita asegurarse de que hacen , no señalan a la misma referencia, entonces necesita usar dup para obtener una nueva copia de la matriz. Si no interfiere con el length de una matriz, las referencias de matriz (ya sean divisiones o referencias a toda la matriz) continuarán refiriéndose alegremente a la misma matriz.

EDITAR: Resulta que los documentos deben actualizarse. Cualquier cosa que pueda cambiar el tamaño de la matriz intentará hacerlo en su lugar si es posible (por lo que podría no reasignarse) pero la reasignará si es necesario para evitar pisotear la memoria de otra matriz o si no tiene suficiente espacio reasignar en su lugar. Por lo tanto, no debería haber ninguna distinción entre cambiar el tamaño de la matriz configurando su propiedad length y redimensionándola añadiéndole.

ADDENDUM: Cualquiera que use D realmente debería leer this article en arreglos y sectores. Los explica bastante bien, y debería darle una mejor idea de cómo funcionan las matrices en D.

+0

Gracias por una respuesta buena y detallada. También escuché acerca de la propiedad .capacity que dice la longitud máxima que la matriz puede tener antes de que necesite una reasignación. Al hacer "auto b = a" obtienen una referencia diferente; & a! = & b, pero parece que "es" usa .ptr bajo el capó para verificar la igualdad referencial. – simendsjo

2

No quería convertir esto en una respuesta en toda regla, pero aún no puedo comentar sobre la respuesta anterior

Creo que la concatenación y la adición son dos operaciones ligeramente diferentes. Si usa ~ con una matriz y un elemento, se agrega; con dos matrices, es concatenación.

Usted podría intentar esto en su lugar:

a = a ~ 2; 

Y ver si obtiene los mismos resultados.

Además, si desea tener un comportamiento definido, simplemente use las propiedades .dup (o .idup for immutables). Esto también es muy útil si tiene una matriz de referencias; puede modificar la matriz principal y las divisiones .dup para seguir trabajando sin preocuparse por las condiciones de carrera.

EDITAR: ok, lo tengo un poco mal, pero de todos modos lo es. Concatenación! = Anexando.

// Max

+0

Sí, alguien más me lo señaló, pero no puedo encontrarlo en la especificación ... Simplemente dice que "una op = b" es lo mismo que "a = a op b". No puedo encontrar la distinción de la matriz en la especificación. – simendsjo

+0

Creo que la recuerdo de TDPL por Andrei (el libro). Al menos estoy seguro de que he leído esto de manera explícita en alguna parte. – awishformore

+1

En realidad, 'op' y' op = 'están sobrecargados por separado, por lo que si los documentos dicen lo contrario, están equivocados. Sobrecarga 'op' con' opBinary' y 'op =' con 'opAssign'. Entonces, teóricamente podrían hacer cosas totalmente separadas (aunque no deberían). Creo que está hecho de esa manera para permitir optimizaciones ya que 'op =' puede hacer cosas en lugar de tener que crear un nuevo valor con el resultado. Por lo tanto, si las matrices no actúan exactamente igual que con '~' como lo hacen con '~ =', no es particularmente especial. –

Cuestiones relacionadas