c++ - tipo - ¿Por qué los operadores de acceso vectorial no se especifican como noexcept?
tipos de variables en c (3)
Como complemento a la respuesta de @SebastianRedl: ¿por qué necesitará noexcept
?
noexcept y std :: vector
Como habrás sabido, un vector
tiene su capacidad. Si está lleno cuando push_back
, asignará una memoria más grande, copiará (o moverá si es C ++ 11) todos los elementos existentes al nuevo troncal, luego agregará el nuevo elemento a la parte posterior.
Pero, ¿qué sucede si se desecha una excepción al asignar memoria o al copiar el elemento en el nuevo troncal?
Si se produce una excepción durante la asignación de memoria, el vector se encuentra en su estado original. Está bien, simplemente vuelva a lanzar la excepción y deje que el usuario la maneje.
Si se produce una excepción durante la copia de los elementos existentes, todos los elementos copiados se destruirán al llamar al destructor, la línea troncal asignada se liberará y la excepción se eliminará para ser manejada por el código de usuario. (1)
Después de destruir todo, el vector vuelve al estado original. Ahora es seguro lanzar una excepción para permitir que el usuario lo maneje, sin perder ningún recurso.
noexcept y mover
Ven a la era de C ++ 11, tenemos un arma poderosa llamada move
. Nos permite robar recursos de objetos no utilizados. std :: vector usará move
cuando necesite aumentar (o disminuir) la capacidad, siempre y cuando la operación de move
sea sin excepción .
Supongamos que se produce una excepción durante el movimiento, el tronco anterior no es lo mismo que antes del move
: los recursos se roban, dejando el vector en un estado roto . El usuario no puede manejar la excepción porque todo está en un estado no determinista.
Es por eso que std::vector
basa en move constructor
para ser noexcept .
Esta es una demostración de cómo el código de cliente se basaría en noexcept
como una especificación de interfaz. Si posteriormente no se cumple el requisito de no noexcept
, cualquier código que dependa de él se romperá.
¿Por qué no simplemente marcar todas las funciones como noexcept
?
Respuesta corta: el código de excepción de seguridad es difícil de escribir.
Respuesta larga: noexcept
establece un límite estricto para el desarrollador que implementa la interfaz. Si desea eliminar el noexcept
de una interfaz, el código del cliente podría romperse como en el ejemplo de vector dado anteriormente; pero si desea crear una interfaz sin noexcept
, puede hacerlo en cualquier momento.
Por lo tanto, solo cuando sea necesario, marque una interfaz como noexcept
.
En Going Native 2013 , Scott Meyers habló sobre la situación anterior de que, sin noexcept
, la cordura de un programa fracasará.
También escribí un blog al respecto: https://xinhuang.github.io/posts/2013-12-31-when-to-use-noexcept-and-when-to-not.html
¿Por qué el operator[]
std::vector
operator[]
, las funciones de los miembros back
y back
no se especifican como noexcept
?
En resumen, hay funciones especificadas con o sin noexcept
. Se pretende, porque son diferentes. El principio es: una función con un comportamiento indefinido especificado (por ejemplo, debido a argumentos impropios) no debe ser sin noexcept
.
Este documento especificó explícitamente que estos miembros no tienen noexcept
. Algunos miembros de vector
fueron utilizados como ejemplos:
Ejemplos de funciones que tienen contratos amplios serían
vector<T>::begin()
yvector<T>::at(size_type)
. Ejemplos de funciones que no tienen un contrato amplio seríanvector<T>::front()
yvector<T>::operator[](size_type)
.
Vea este documento para la motivación inicial y la discusión detallada. El problema realista más obvio aquí es la capacidad de prueba.
La política estándar de noexcept
es marcar solo las funciones que no pueden o no deben fallar, pero no las que simplemente se especifican para no lanzar excepciones. En otras palabras, todas las funciones que tienen un dominio limitado (pasan los argumentos incorrectos y obtienes un comportamiento indefinido) no son noexcept
, incluso cuando no están especificadas para lanzarlas.
Las funciones que se marcan son cosas como el swap
(no deben fallar, porque la seguridad de la excepción a menudo se basa en eso) y numeric_limits::min
(no puede fallar, devuelve una constante de un tipo primitivo).
El motivo es que los implementadores pueden querer proporcionar versiones especiales de depuración de sus bibliotecas que se presenten en diversas situaciones de comportamiento indefinido, de modo que los marcos de prueba puedan detectar fácilmente el error. Por ejemplo, si usa un índice fuera de límite con vector::operator[]
, o llama al front
o back
en un vector vacío. Algunas implementaciones quieren lanzar una excepción allí (a la que se les permite: dado que es un comportamiento indefinido, pueden hacer cualquier cosa), pero un noexcept
obligatorio de esas funciones lo hace imposible.