Cuando noexcept no es solo una moda, sino tu herramienta principal en 2025
\n\nImagina: llevas años programando en C++, el compilador calla, el código funciona. Pero de repente notas que std::vector al insertar nuevos elementos copia los objetos, aunque claramente definiste el move-constructor. ¿Un dolor familiar? Bienvenido al mundo donde noexcept decide el destino del rendimiento.
La pregunta «¿Cuándo debería usar realmente noexcept?» en Stack Overflow ha acumulado miles de visitas, y con razón. En 2025, cuando los compiladores son aún más inteligentes y la biblioteca estándar es aún más exigente con las garantías, el uso incorrecto de noexcept pasa de ser una microoptimización a un error crítico. Analicemos dónde este especificador es tu mejor amigo y dónde es ruido innecesario.
\n\nBreve introducción: ¿qué es noexcept y para qué sirve?
\n\nnoexcept es un especificador que le dice al compilador: «Esta función nunca lanzará una excepción». En C++17 se convirtió en parte del sistema de tipos, y en C++20 ganó aún más influencia en las optimizaciones.
Cuando escribes:
\nvoid safe_function() noexcept {\n // código que garantiza que no lanza excepciones\n}\n\nEl compilador recibe luz verde para:
\n- \n
- Generar código más compacto (sin sobrecarga de desenrollado de pila) \n
- Optimizar la semántica de movimiento en contenedores \n
- Simplificar la lógica para
std::terminateen caso de una excepción real \n
Pero lo principal es que noexcept afecta el comportamiento de los contenedores estándar. Y aquí está el truco más común.
\n\nRegla n.º 1: El move-constructor y el operador de asignación move deben ser noexcept
\n\nEsta es la regla de oro de C++11 y todos los estándares posteriores. Si tu move-constructor no está marcado como noexcept, std::vector al reasignar memoria copiará los elementos, no los moverá. ¿Por qué?
Porque std::vector debe proporcionar una garantía fuerte de seguridad de excepciones. Si la operación move puede lanzar una excepción, el contenedor no podrá revertir al estado original. La copia es segura: si la copia falla, el objeto original permanece intacto.
Un ejemplo que vale la pena recordar:
\n#include <vector>\n#include <string>\n#include <iostream>\n\nstruct MyData {\n std::string name;\n \n // Move-constructor sin noexcept\n MyData(MyData&& other) : name(std::move(other.name)) {\n // Todo bien, ¡pero no tiene noexcept!\n }\n};\n\nint main() {\n std::vector<MyData> vec;\n MyData d1{"Alice"}, d2{"Bob"};\n vec.push_back(std::move(d1));\n vec.push_back(std::move(d2)); \n // Atención: al reasignar memoria\n // ¡los elementos se copiarán, no se moverán!\n std::cout << "Tamaño: " << vec.size() << std::endl;\n return 0;\n}\n\nLa corrección es agregar noexcept:
MyData(MyData&& other) noexcept : name(std::move(other.name)) {}\n\nConclusión: Todas las operaciones move que no lanzan excepciones (y normalmente no lo hacen, si no asignan memoria manualmente) deben marcarse con noexcept. No es una opción, es una necesidad para el rendimiento.
Regla n.º 2: Las operaciones swap son otro candidato para noexcept
\n\nLas funciones swap se usan a menudo en algoritmos y contenedores. Si swap puede lanzar una excepción, muchos algoritmos (como las ordenaciones) pierden eficiencia.
class Buffer {\n int* data;\n size_t size;\npublic:\n friend void swap(Buffer& a, Buffer& b) noexcept {\n using std::swap;\n swap(a.data, b.data);\n swap(a.size, b.size);\n }\n};\n\n¿Por qué es importante? Porque std::sort, std::shuffle y otras