2010-09-30 11 views
7

Hace un tiempo me dijeron que era un lugar común para usar std :: vector como una matriz dinámica segura de excepción en C++ en lugar de asignar matrices sin formato ... por ejemploUsando operator [] en empty std :: vector

{ 
    std::vector<char> scoped_array (size); 
    char* pointer = &scoped_array[0]; 

    //do work 

} // exception safe deallocation 

he utilizado esta convención varias veces sin problemas, sin embargo, recientemente portado algo de código para Win32 VisualStudio2010 (antes era sólo en MacOS/Linux) y mis pruebas unitarias están rompiendo (stdlib emite una afirmar) cuando el tamaño del vector es cero.

Entiendo que escribir en una matriz de este tipo sería un problema, pero esta suposición rompe esta solución como un reemplazo de los punteros sin formato. Considere las siguientes funciones con n = 0

void foo (int n) { 
    char* raw_array = new char[n]; 
    char* pointer = raw_array; 
    file.read (pointer , n); 
    for (int i = 0; i < n; ++i) { 
     //do something 
    } 
    delete[] raw_array; 
} 

Si bien podría decirse redundante, el código anterior es perfectamente legal (creo), mientras que el código de abajo va a lanzar una afirmación en VisualStudio2010

void foo (int n) { 
    std::vector<char> scoped_array (n); 
    char* pointer = &scoped_array[0]; 
    file.read (pointer , n); 
    for (int i = 0; i < n; ++i) { 
    //do something 
    } 
} 

Era Estoy usando un comportamiento indefinido todo el tiempo? Yo tenía la impresión de operador [] hizo ninguna comprobación de errores, y esto era un uso válido de std :: vector <>. ¿Alquien más se ha encontrado con este problema?

--edit: Gracias por todas las respuestas útiles, en respuesta a la gente diciendo que es un comportamiento indefinido. ¿Hay alguna forma de reemplazar la asignación de matrices sin formato que se encuentra arriba con n = 0?

Si bien decir que la comprobación de n = 0 como caso excepcional se resolverá el problema (lo hará). Hay muchos patrones que no se necesita caso especial (como el ejemplo puntero del crudo por encima) así que tal vez utilizando algo más que std :: vector <> serían necesarios?

+1

¿Qué excepción se produce? No veo nada malo con el código. – fschmitt

+2

@fschmitt: si el vector está vacío, 'scoped_array [0]' proporciona un comportamiento indefinido. En este caso, construir una variante de depuración con Visual C++, falla una comprobación de rango y arroja una excepción. –

+0

@fschmitt: La implementación de Visual Studio de std :: vector desencadena una afirmación (es decir, mata todo el proceso de forma inmediata) con un error que indica que hubo un error de fuera de límites en el vector. – Akusete

Respuesta

8

Ver LWG issue 464. Este es un problema conocido. C++ 0x (implementado parcialmente por MSVC 2010) lo resuelve agregando un miembro .data().

+0

.data() sería perfecto, lástima solo en C++ 0x – Akusete

+0

Estoy aceptando esta respuesta simplemente porque hace referencia a la discusión sobre por qué se quiere este tipo de operación, y cuál será (eventualmente) un estándar forma de hacerlo – Akusete

0

MVS no cubre la comprobación en operator[] incluso en las versiones de lanzamiento. No sé si cumple con las normas. (De hecho, encontré depurar código en su aplicación que hizo su ruptura implementación del código correcto). Sin embargo, hay un interruptor para deshabilitarlo.

+0

No es conforme al estándar pero es muy útil para depurar código sin terminar rápidamente.Desafortunadamente, esto dio lugar a la necesidad de sintonizar una maraña de macros para obtener el comportamiento deseado. No sé si esto es más limpio en las versiones posteriores (VC9/VC10). –

+0

@Steve: ¿Quiere decir que el estándar * prohíbe * el operador 'verificado []' haciendo '& vec [0]' siempre válido? ¿Tiene una referencia donde está escrito? Editar: no importa, Cubbi respondió esta pregunta. – ybungalobill

0

Si desea obtener un comportamiento más limpio en este escenario, puede reemplazar el uso de a[0] con el uso a.at(0), que arrojará si el índice no es válido.

una solución pragmática sería a init vector con n entradas + 1 y restringir el acceso a 0..n-1 (ya que este código ya hace).

void foo (int n) { 
    std::vector<char> scoped_array (n+1); 
    char* pointer = &scoped_array[0]; 
    file.read (pointer , n); 
    for (int i = 0; i < n; ++i) { 
    //do something 
    } 
} 
+0

Eso es lo contrario de lo que realmente quiere, sin embargo. Él quiere que no arroje, al igual que el código de matriz/puntero no arroja. –

+0

@Steve Jessop - Leí el objetivo como 'matriz dinámica segura de excepciones en C++'. El código en su forma actual afirmará (en el mejor de los casos) y la culpa (más típicamente). –

+0

En lo que respecta al código de ejemplo, creo que la palabra clave es * array *. Una matriz proporciona un puntero de un paso al pasado, que legalmente puede pasar a 'leer ', siempre que el número de bytes leídos sea 0. Un vector no le da ese puntero, solo una pasada, la -end * iterador *. Entonces, en ese sentido, no es una matriz dinámica segura de excepciones. Creo que el código de vector de Akusete o bien afirmará o bien funcionará. No sé exactamente cómo es la implementación de la que realmente fallará, aunque estamos bastante seguros de que está permitida. –

0

Esto trajo una pregunta interesante, en mi opinión, que rápidamente le pregunté here. En su caso, se puede evitar el uso de punteros de la siguiente manera:

template<class InputIterator, class OutputIterator> 
OutputIterator copy_n(InputIterator first, InputIterator last, OutputIterator result, std::size_t n) 
{ 
    for (std::size_t i = 0; i < n; i++) { 
     if (first == last) 
      break; 
     else 
      *result++ = *first++; 
    } 
    return result; 
} 

std::ifstream file("path_to_file"); 
std::vector<char> buffer(n); 
copy_n(std::istream_iterator<char>(file), 
     std::istream_iterator<char>(), 
     std::back_insert_iterator<vector<char> >(buffer), 
     n); 

Esto copia el contenido del archivo en una memoria intermedia n caracteres a la vez. Cuando itere sobre el búfer, use:

for (std::vector<char>::iterator it = buffer.begin(); it != buffer.end(); it++) 

en lugar de un contador.

4

En lo que respecta al estándar C++, operator[] no se garantiza que no se compruebe, es solo que (a diferencia de at()) no está garantizado para comprobar.

Es de esperar que en una implementación sin comprobación, &scoped_array[scoped_array.size()] daría como resultado un puntero legal dentro o fuera de una matriz asignada por el vector. Esto no está explícitamente garantizado, pero para una implementación dada, usted puede verificarlo mirando su fuente. Para un vector vacío, puede que no haya ninguna asignación (como optimización) y no veo nada en la parte vector del estándar que define el resultado de scoped_array[0] que no sea la tabla 68.

Pasando de tabla 68, puede decir que el resultado de su expresión es &*(a.begin() + 0), que desreferencia de forma ilegal un iterador de extremo a extremo. Si el iterador de vector de su implementación es solo un puntero, probablemente se salga con la suya, si no, no, y obviamente el suyo no.

Olvidé los resultados del argumento de si &*, en un puntero que no se debe desreferenciar, no funciona o no. IIRC no está claro en el estándar (alguna ambigüedad en alguna parte), lo que provocó solicitudes para arreglar el estándar para hacerlo explícitamente legal. Esto sugiere que de hecho funciona en todas o la mayoría de las implementaciones conocidas.

Personalmente, no confío en esto, y no desactivo la comprobación. Me reescribir su código:

char* pointer = (scoped_array.size() > 0) ? &scoped_array[0] : 0; 

O en este caso simplemente:

char* pointer = (n > 0) ? &scoped_array[0] : 0; 

Sólo se ve mal a mí utilizar el índice n de un vector sin saber que el tamaño es al menos n + 1, independientemente de si realmente funciona en su implementación una vez que haya desactivado la verificación.

+0

Acepto, mejor reescribir este código que tratar de evitar el cheque, está ahí por una razón. –

1

operator [] devuelve una referencia y, por lo tanto, invocarla en un vector vacío no debe definirse.

Después de todo, ¿a qué artículo se debe hacer referencia cuando no hay elementos? operator [] tendría que devolver una referencia nula o una referencia totalmente inválida. Ambos resultados resultarían en un comportamiento indefinido.

Así que sí, estuvo usando un comportamiento indefinido todo el tiempo. Las comprobaciones no obligatorias pero constantes de Visual Studio en operator [] acaban de revelar ese hecho.

0

¿Podría usar iteradores en lugar de punteros?

{ 
    std::vector<char> scoped_array (size); 
    std::vector<char>::iterator pointer = scoped_array.begin(); 

    //do work 

} // exception safe deallocation 
+0

En general, no, estaba buscando una manera de asignar matrices de punteros normales de una manera segura de excepción (algo así como auto_ptr para una matriz). Tenía la impresión de que era una buena práctica usar un estándar para tal fin. – Akusete