¿Cómo determino a partir de la documentación qué tipo de excepción puede lanzar una función?

8 minutos de lectura

avatar de usuario
Adán Hartleb

Soy nuevo en la programación de C ++, pero he programado en lenguajes de nivel superior para orientarme en la mayoría de la documentación. Estoy aprendiendo sobre el manejo de excepciones en C++, específicamente con este ejemplo:

vector<int> myNums;

try
{
    myNums.resize(myNums.max_size() + 1);
}
catch (const bad_alloc& err)
{
    cout << err.what() << endl;
}

Este código no detecta la excepción porque la excepción lanzada por el método .resize() no es bad_alloc; es un length_error. Entonces, a partir de esta documentación, ¿cómo se llega a eso? Tal vez me perdí algo obvio.

https://cplusplus.com/reference/vector/vector/resize/

La única excepción específica mencionada allí es bad_alloc. ¿Puede alguien explicarme cómo llegaría a saber eso? length_error ¿La excepción correcta comienza en esa página?

  • Es interesante. es.cppreference.com/w/cpp/error/length_error dice claramente “Esta excepción es lanzada por las funciones miembro de std::basic_string y std::vector::reserve”, pero llamó a std::vector::resize, por lo que incluso leer esa página no le daría la pista que está buscando.

    – Jerry Jeremías

    24 de agosto a las 1:59


  • Ver también intentar bloquear y la cláusula catch-all catch (...) que coincide con cualquier excepción. (insinuación: cppreference.com es la referencia de C++ a usar)

    –David C. Rankin

    24 de agosto a las 2:35

  • Actualicé cppreference en vector::resize – es una wiki pública y así es como mejora

    – Cubbi

    24 de agosto a las 18:28

  • Este tipo de cosas es la razón por la que los idiomas necesitan integrar posibles valores de retorno de error usando su sistema de tipos, para que pueda ser completamente automático y exhaustivamente verificado estáticamente.

    – iono

    25 de agosto a las 9:49


  • @iono: Sí, a todo el mundo le encantan las excepciones comprobadas de Java. 😉

    – Heinzi

    25 de agosto a las 12:24

Esto no es raro. La complejidad del lenguaje ha aumentado tanto a lo largo de los años, acumulando múltiples revisiones del estándar C++, que incluso el propio estándar C++ puede estar en desacuerdo consigo mismo, a veces.

Veamos qué dice el propio estándar C++ sobre dos versiones de la sobrecarga resize() método vectorial. Resulta que tengo una copia de N4860 a mano que es, básicamente, la versión C++ 20, y mientras busco lo que dice el estándar C++ sobre resize()excepciones, encontré que los dos resize() Las sobrecargas definen su comportamiento de excepción de la siguiente manera:

constexpr void redimensionar(size_type sz);

// …

Observaciones: si se lanza una excepción que no sea el constructor de movimiento de un T no Cpp17CopyInsertable, no hay efectos.

// …

constexpr void resize(size_type sz, const T& c);

// …

Observaciones: Si se lanza una excepción, no hay efectos.

Esa es la única mención de excepciones en resize(). No encontré nada en un más general en vector en sí mismo, ni en “Requisitos del contenedor”, hubo alguna discusión sobre las garantías de excepción, pero ninguna relacionada con los detalles específicos de los vectores. resize() o reserve().

Este es un descuido obvio. Es bastante obvio que cuando se trata de excepciones que pueden generarse como resultado de reasignaciones, ambas sobrecargas deberían tener el mismo comportamiento de excepción. La descripción de la primera sobrecarga se extrae directamente de reserve() que justo lo precede. No hace falta decir que resize() usos reserve() para hacer crecer la capacidad del vector, cuando sea necesario, y hereda sus garantías/comportamiento de excepción.

Pero lo mismo debe ser cierto con el segundo resize() sobrecarga. La única diferencia entre ellos es que uno construye por defecto nuevos valores cuando el vector crece y el otro copia-construye. Pero en términos de comportamiento de excepción durante la reasignación, deben ser idénticos. La diferencia general total, relacionada con las excepciones, entre las dos sobrecargas se debe a las diferencias de excepción entre el constructor predeterminado del valor y/o sus constructores de copiar/mover.

Mi pregunta es, al mirar esta documentación, ¿cómo se llega a eso? > Tal vez me perdí algo obvio.

No, no te perdiste nada. El estándar C++ en sí mismo tiene algunas lagunas; sin mencionar las fuentes de documentación de segunda mano como la que está viendo.

Llegas a donde quieres ir estudiando todo sobre la clase, la plantilla o el algoritmo en cuestión, entendiendo cómo debe funcionar, es decir, el resize()s heredar ciertas partes de su comportamiento de reserve() — y luego sacar las inferencias ineludibles.

TLDR: es lo que es.

  • Una respuesta que solo la experiencia puede enseñar…

    –David C. Rankin

    24 de agosto a las 2:38

  • No creo que “no hace falta decir” que resize llamadas reserve. Aquí reserve+resize y simple resize hacen cosas diferentes: coliru.stacked-crooked.com/a/434b5e92dff1575f Por supuesto, comparten código, ya que ambos pueden tener que reasignar el vector que cae en allocator_traits::allocate (que es donde se especifica bad_alloc). En cuanto a las especificaciones, ya que resizes no tienen una cláusula Throws: explícita, pueden lanzar cualquier excepción

    – Cubbi

    24 ago a las 18:00


Empezando con https://cplusplus.com/reference/vector/vector/resize/si tienes un caso como el tuyo empujando max_size()puede prestar especial atención al caso enumerado en esta página de documento que establece:

Si n también es mayor que la capacidad actual del contenedor, tiene lugar una reasignación automática del espacio de almacenamiento asignado.

Dado que su caso va a ser absolutamente mayor que la capacidad actual del contenedor, podría valer la pena analizarlo. Vinculada en este fragmento de texto está la página del documento para la capacidad: https://cplusplus.com/reference/vector/vector/capacity/. En la página de capacidad, leería que vector::reserve se utiliza para aumentar explícitamente la capacidad del vector. Desde su caso con max_size() + 1 ciertamente implicará aumentar la capacidad del vector, es posible que sospeche que esta función está involucrada. Así que podrías ir a la página del documento: https://en.cppreference.com/w/cpp/container/vector/reserve

Aquí lo leerías vector::reserve toma un parámetro new_cap que determina la nueva capacidad de un vector. tira length_error cuando new_cap > max_size().

No doy esta serie de pasos porque creo que cualquiera esperaría/debería indagar tanto en los documentos cada vez que escriben código. Solo porque tenía curiosidad sobre qué pasos podrían haberlo llevado a la excepción que se lanzó.

Estoy de acuerdo en que sería mucho mejor si la documentación para resize acabo de cubrir todas sus bases con respecto a qué excepciones se lanzan en todos los casos. Desafortunadamente, esto es muy común con respecto a la documentación.

avatar de usuario
Pedro

Tienes que cavar un poco para encontrarlo.

Voy a usar la documentación en cppreference, que hace un trabajo razonablemente decente al rastrear lo que sucede en los estándares. (Los estándares son la fuente autorizada, pero los estándares han evolucionado con el tiempo).

De acuerdo a https://en.cppreference.com/w/cpp/container/vectorel segundo argumento de la plantilla cuando se crea una instancia de un vector es un tipo de asignador, cuyo valor predeterminado es std::allocator<T> (dónde T es el tipo de elemento del vector).

Debido a que el asignador está predeterminado, a menudo no se lo menciona explícitamente en el código de usuario (la mayoría de los desarrolladores no necesitan usar un asignador no predeterminado).

Pero

std::vector<int> myNums;

en realidad es equivalente a

std::vector<int, std::allocator<int> > myNums;

La especificación de un vectors resize() La función miembro describe lo que sucede cuando arroja, pero no las circunstancias en las que arrojará, o lo que puede arrojar.

Asignación de memoria para std::vector en realidad es manejado por su reserve() función miembro. Documentación para esa función en https://en.cppreference.com/w/cpp/container/vector/reserve estados que arroja std::length_error si new_cap > max_size()o cualquier excepción lanzada por Allocator::allocate() (típicamente std::bad_alloc). Allocator es el nombre del segundo parámetro de plantilla mencionado anteriormente.

Esa es una pista, pero podemos ser aún más específicos profundizando en la documentación del asignador predeterminado en https://en.cppreference.com/w/cpp/memory/allocator y por su allocate() función miembro en https://en.cppreference.com/w/cpp/memory/allocator/allocate lo que revela que la función arrojará std::bad_alloc si la asignación falla.

En lugar de leer la documentación o el código para encontrar una respuesta que puede no ser correcta, creo que la mejor opción es hacer algo que debería estar haciendo de todos modos: escribir una prueba.

Si prueba a fondo su código, las excepciones que arroja se harán evidentes de forma natural y podrá manejarlas.

¿Ha sido útil esta solución?