Después de darle unas cuántas vueltas creo que he llegado a una explicación. Si alguien ve algo raro o incorrecto que me lo comente para corregirlo ya que creo que puede servir para gente que tenga dudas con esto
Voy a usar un ejemplo muy sencillo para explicar esto:
void anular(int *v, int pos){
v[pos] = 0;
}
int main(){
int *p;
p = new int [2];
p[0] = 1;
p[1] = 2;
anular(p,1);
}
En la línea 6, declaramos un puntero (almacena una dirección de memoria) <p> y hacemos que apunte a <NULL>. Vamos a suponer que se guarda en la posición de memoria 0x1 (voy a usar posiciones de memoria consecutivas para simplificar el ejemplo). Entonces quedaría así:
En la línea 7, reservamos memoria para guardar dos enteros. Suponemos que sus posiciones son 0x2 y 0x3 respectivamente y en las líneas 8 y 9 asignamos valores a esas posiciones. Entonces tenemos:
0x1(p) = 0x2
0x2(p[0]) = 1
0x3(p[1]) = 2
En la línea 10, llamamos a la función <anular()>, la cual recibe el puntero que almacena la dirección de memoria del primer elemento del array. Al ser pasado por valor, se copia el contenido de <p> en <v> (suponemos <v> en 0x4) y el 1 se copia en <pos> (suponemos 0x5). Entonces tenemos antes de ejecutar la función:
0x1(p) = 0x2
0x2(p[0]) = 1
0x3(p[1]) = 2
0x4(v) = 0x2
0x5(pos) = 1
Al ejecutar la línea 2, el contenido se modifica en el "array original" (que es siempre el mismo porque sólo tenemos uno). Esto es porque <v> almacena la misma posición de memoria que <p> (OJO: <v> no almacena la posición de memoria dónde se guarda <p>, sino la dirección de memoria que se guarda EN <p>).
Por eso para hacer cambios o acciones sobre el array en sí, no es necesario pasarlo por referencia, porque ambos punteros apuntan al mismo sitio.
Sin embargo, si tenemos esta alternativa:
void reservar(int *&v, int size){
v = new int [size];
}
Y queremos reservar memoria, esto es una operación que se debe aplicar, siguiendo con el mismo ejemplo, sobre 0x1 (dirección en la que hemos guardado <p>), no sobre 0x4 (dirección en la que hemos guardado <v>). Al pasar por referencia la tabla de direcciones de memoria anterior quedaría de la siguiente manera:
0x1(p) = 0x2
0x2(p[0]) = 1
0x3(p[1]) = 2
0x1(v) = 0x2 (no hemos copiado el valor de <p> en otra direccion de memoria sino que usamos la direccion de memoria de <p>
0x5(pos) = 1
Y entonces ahora sí que podemos aplicar la orden de reserva sobre <v> ya que en realidad la estamos aplicando sobre <p> (realmente se lo estamos aplicando a la dirección 0x1).
Entonces mi conclusión es que el paso por referencia es para operar sobre la dirección de memoria DÓNDE está el puntero, no para operar sobre la dirección de memoria que está EN el puntero. Por lo que supongo que a efectos de "eficiencia", es lo mismo hacer un paso por referencia que por valor (en ambos casos pasamos únicamente una dirección de memoria, en un caso la dirección dónde está el puntero y en el otro, la dirección que guarda el puntero) Espero que le sirva a alguien.