Versiones de C++, compiladores y parches: qué trajo cada versión del lenguaje, de C++98 a C++23, con un programa compilado para cada una; qué ha pasado con C++26, que no superó la votación de ISO en octubre de 2026; qué versión usa tu compilador; cómo endurecer un ejecutable con -fhardened y el modo endurecido de la biblioteca; y por qué la seguridad de memoria es hoy el gran debate del lenguaje.
En C++, como en C, «estar al día» depende de tres piezas: la versión del lenguaje (qué se puede escribir), el compilador (GCC, Clang o MSVC) y la biblioteca estándar que lo acompaña (libstdc++ con GCC, libc++ con Clang en macOS, la STL de Microsoft en Windows), además de las bibliotecas de terceros. Esta página repasa qué trajo cada versión, con un programa compilado para cada una, cómo saber cuál usas, qué opciones endurecen un ejecutable y por qué la seguridad de memoria se ha convertido en el gran debate del lenguaje.
De C with Classes al primer estándar
Bjarne Stroustrup empezó en 1979, en los Bell Labs, lo que llamó «C with Classes»: C con las clases de Simula, para escribir programas grandes sin renunciar a la velocidad de C. A finales de 1983 pasó a llamarse C++ (el operador ++ de C: «uno más que C»), y en octubre de 1985 salió la primera versión comercial, el compilador Cfront, que traducía C++ a C, junto con la primera edición de su libro The C++ Programming Language. El primer estándar ISO llegó en 1998.
Qué trajo cada versión
- VersiónQué trajo
- C++98 / C++03El primer estándar (ISO/IEC 14882:1998) y su corrección de 2003: plantillas, excepciones, espacios de nombres y la biblioteca estándar con
std::string,vector,mapy los algoritmos (la STL). - C++11La gran renovación, el «C++ moderno»:
auto, lambdas, elforpor rangos,nullptr, inicialización con llaves, semántica de movimiento,unique_ptryshared_ptr,constexpr, hilos y atómicos,<chrono>,<random>y<regex>. - C++14Pulió C++11: lambdas genéricas,
std::make_unique, deducción del tipo devuelto, literales binarios y separadores de dígitos (0b1010,1'000'000). - C++17Enlaces estructurados (
auto [a, b] = par;),std::optional,std::variant,std::string_view,if constexpr,ifcon inicializador ystd::filesystem. - C++20La segunda gran renovación: conceptos, rangos, corrutinas y módulos, el operador
<=>,std::format,std::spane inicializadores designados. - C++23
std::printystd::println,std::expected,std::ranges::to,std::mdspan, el «deducing this» y el móduloimport std;. Es la versión vigente: ISO/IEC 14882:2024. - C++26 (sin aprobar)Reflexión en tiempo de compilación, contratos (
pre,postycontract_assert),std::executionpara programación asíncrona, una biblioteca estándar endurecida que comprueba los límites, lectura «errónea» en lugar de indefinida de variables sin inicializar,#embede indexación de paquetes de plantillas.
C++26: terminado, pero rechazado en la votación de ISO
El comité de C++ (el WG21 de ISO) cerró las novedades de C++26 en junio de 2025 y dio por terminado el borrador en marzo de 2026. Pero en la votación final de los organismos nacionales, cerrada el 23 de octubre de 2026, el borrador no se aprobó: según el recuento que difundió el ingeniero Mateusz Pusz, solo 12 de los 20 países con derecho a voto pleno dijeron que sí (hacían falta dos tercios) y nueve votaron que no, entre ellos España, Italia, Japón y Suecia. Es la primera vez que le pasa a una versión de C++. Los comentarios de los países aún no se han publicado, pero todo apunta a los contratos, la novedad más polémica, que ya se cayeron de C++20. El comité decidirá qué hacer en su reunión de noviembre en Búzios (Brasil): quitar los contratos y volver a votar, votar otra vez sin cambios o sacarlos a un documento aparte. Si la publicación se va a 2027, no se descarta que acabe llamándose C++27.
Para quien programa, el efecto inmediato es pequeño: los compiladores ya implementan lo que hay en el borrador detrás de -std=c++26 (o -std=c++2c), y la votación no cambia lo que ya funciona. La versión oficial sigue siendo C++23.
Qué versión usa tu compilador
La macro __cplusplus dice qué versión del lenguaje está aplicando el compilador, como un número con el año y el mes:
#include <iostream>
int main()
{
#if __cplusplus >= 202302L
std::cout << "C++23 o posterior (" << __cplusplus << ")\n";
#elif __cplusplus >= 202002L
std::cout << "C++20 (" << __cplusplus << ")\n";
#else
std::cout << "Anterior a C++20 (" << __cplusplus << ")\n";
#endif
}salidaC++23 o posterior (202302)
Ese programa se compiló con -std=c++23. Estos son los valores que da GCC 15.3 con cada opción, y lo que usan g++ y clang++ si no se dice nada:
for std in c++98 c++03 c++11 c++14 c++17 c++20 c++23 c++26; do echo | g++ -std=$std -x c++ -dM -E - | grep __cplusplus; done
salidac++98 199711L c++03 199711L c++11 201103L c++14 201402L c++17 201703L c++20 202002L c++23 202302L c++26 202400L
por omisióng++ 15.3 (por omisión) 201703L clang++ 19 (por omisión) 201703L
Esto sorprende a mucha gente: GCC 15 y Clang 19 siguen compilando en C++17 si no les pides otra cosa, aunque soporten casi todo C++23. GCC 16, de 2026, pasó por fin a C++20 por omisión. Por eso conviene fijar la versión siempre de forma explícita: -std=c++23 al compilar a mano, o set(CMAKE_CXX_STANDARD 23) en CMake, como en la lección 8. El valor 202400 de C++26 es provisional: el definitivo se fija cuando se publica la versión.
Cada versión, en un programa
C++11: el C++ moderno
Este programa usa cinco novedades de C++11: inicialización con llaves, una lambda, el for por rangos, auto, shared_ptr y nullptr.
#include <iostream>
#include <memory>
#include <vector>
int main()
{
std::vector<int> v{3, 1, 2};
auto doble = [](int x) { return x * 2; };
for (auto x : v)
std::cout << doble(x) << ' ';
std::cout << '\n';
auto p = std::make_shared<int>(42);
int *nulo = nullptr;
std::cout << *p << ' ' << (nulo == nullptr) << '\n';
}salida6 2 4 42 1
El mismo código, guardado como cpp11_en_98.cpp y compilado como C++98, no llega ni a empezar. Estos son los primeros avisos y errores:
g++ -std=c++98 -Wall cpp11_en_98.cpp -o cpp11_en_98
g++: errorcpp11_en_98.cpp:14:17: warning: identifier 'nullptr' is a keyword in C++11 [-Wc++11-compat]
14 | int *nulo = nullptr;
| ^~~~~~~
cpp11_en_98.cpp: In function 'int main()':
cpp11_en_98.cpp:7:23: warning: extended initializer lists only available with '-std=c++11' or '-std=gnu++11' [-Wc++11-extensions]
7 | std::vector<int> v{3, 1, 2};
| ^
cpp11_en_98.cpp:7:22: error: in C++98 'v' must be initialized by constructor, not by '{...}'
7 | std::vector<int> v{3, 1, 2};
| ^
cpp11_en_98.cpp:7:31: error: no matching function for call to 'std::vector<int>::vector(<brace-enclosed initializer list>)'
7 | std::vector<int> v{3, 1, 2};C++14: lambdas genéricas y números legibles
#include <iostream>
#include <memory>
int main()
{
auto mayor = [](auto a, auto b) { return a > b ? a : b; };
std::cout << mayor(3, 7) << ' ' << mayor(2.5, 1.5) << '\n';
auto p = std::make_unique<long>(1'000'000);
int mascara = 0b1010;
std::cout << *p << ' ' << mascara << '\n';
}salida7 2.5 1000000 10
C++17: enlaces estructurados, optional y string_view
#include <iostream>
#include <map>
#include <optional>
#include <string>
#include <string_view>
std::optional<int> buscar(const std::map<std::string, int>& m, std::string_view clave)
{
if (auto it = m.find(std::string(clave)); it != m.end())
return it->second;
return std::nullopt;
}
int main()
{
std::map<std::string, int> edades{{"Ana", 31}, {"Luis", 28}};
for (const auto& [nombre, edad] : edades)
std::cout << nombre << ": " << edad << '\n';
std::cout << buscar(edades, "Eva").value_or(-1) << '\n';
}salidaAna: 31 Luis: 28 -1
C++20: conceptos, rangos, <=> y format
#include <compare>
#include <concepts>
#include <format>
#include <iostream>
#include <ranges>
#include <vector>
template <std::integral T>
T cuadrado(T x) { return x * x; }
struct Version {
int mayor, menor;
auto operator<=>(const Version&) const = default;
};
int main()
{
std::vector<int> v{1, 2, 3, 4, 5, 6};
for (int x : v | std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform(cuadrado<int>))
std::cout << x << ' ';
std::cout << '\n';
std::cout << std::format("{} {}\n", Version{20, 1} < Version{23, 0}, cuadrado(12));
}salida4 16 36 true 144
El concepto std::integral hace que cuadrado solo acepte enteros: si le pasas un double, el error dice directamente que no se cumple la restricción, en lugar de las decenas de líneas que daban las plantillas antes de C++20.
C++23: print y expected
#include <expected>
#include <print>
#include <ranges>
#include <string>
#include <vector>
std::expected<int, std::string> dividir(int a, int b)
{
if (b == 0)
return std::unexpected("división entre cero");
return a / b;
}
int main()
{
for (int b : {4, 0}) {
auto r = dividir(20, b);
if (r)
std::println("20 / {} = {}", b, *r);
else
std::println("error: {}", r.error());
}
auto pares = std::views::iota(1, 11) | std::views::filter([](int n) { return n % 2 == 0; })
| std::ranges::to<std::vector>();
std::println("{}", pares);
}salida20 / 4 = 5 error: división entre cero [2, 4, 6, 8, 10]
std::println por fin escribe con el formato de std::format y sabe imprimir un vector entero. std::expected devuelve un resultado o un error, sin excepciones: lo vemos en la lección 7.
C++26: lo que ya funciona en GCC 15
Aunque C++26 no esté aprobado, GCC 15 ya trae algunas de sus novedades con -std=c++26. Dos pequeñas: indexar un paquete de parámetros de plantilla con ...[0] y usar _ como nombre para lo que no importa, tantas veces como se quiera.
#include <print>
template <typename... T>
auto primero(T... args) { return args...[0]; }
int main()
{
auto _ = primero(7, 8, 9);
auto _ = primero("hola", "adiós");
std::println("{} {}", primero(7, 8, 9), primero("hola", "adiós"));
}salida7 hola
Y otra muy práctica: al borrar una función con = delete se puede decir por qué, y el compilador lo muestra en el error.
#include <cstring>
void copiar(char *destino, const char *origen) = delete("usa std::string o strncpy con el tamaño");
int main()
{
char buf[8];
copiar(buf, "demasiado largo");
}g++: errorcpp26_delete.cpp: In function 'int main()':
cpp26_delete.cpp:8:11: error: use of deleted function 'void copiar(char*, const char*)': usa std::string o strncpy con el tamaño
8 | copiar(buf, "demasiado largo");
| ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~Las novedades grandes, la reflexión y los contratos, llegaron en GCC 16 y todavía se consideran experimentales.
Los tres compiladores
- GCC (
g++), de GNU: el de casi todas las distribuciones de Linux, con la biblioteca libstdc++. Saca una versión mayor al año, en primavera. Tiene C++20 completo y casi todo C++23. - Clang (
clang++), del proyecto LLVM: el de macOS (con su biblioteca libc++), Android y FreeBSD, y la base de muchas herramientas, como clang-tidy y clangd. Saca una versión mayor cada seis meses. En Linux suele usar la libstdc++ de GCC. - MSVC, de Microsoft, el de Visual Studio, con su propia biblioteca estándar: se elige la versión con
/std:c++20o/std:c++latestpara lo más nuevo. En Windows también se usan GCC (MSYS2) y Clang.
Ningún compilador implementa una versión nueva de golpe: cada novedad llega por separado. La referencia para saber qué soporta cada uno son las tablas de cppreference y la página de estado de GCC.
Cómo se mantiene al día un programa en C++
Lo esencial es lo mismo que en C (está en la página de versiones de C): actualizar el sistema y reiniciar los servicios, vigilar las bibliotecas de terceros y compilar con protecciones. C++ añade dos cosas:
- La biblioteca estándar va con el compilador. libstdc++ se actualiza con el sistema en Linux, pero si distribuyes tu programa compilado en estático, o con su propia copia de la biblioteca, cada corrección te obliga a recompilar.
- Las bibliotecas de terceros suelen ir dentro del proyecto. Muchas bibliotecas de C++ son solo cabeceras (header-only) y se copian en el código; los gestores como vcpkg y Conan ayudan a saber qué versión usas y a actualizarla.
Las opciones de endurecimiento
Desde GCC 14, la opción -fhardened activa de una vez casi todas las protecciones del compilador. Esto es lo que activa en GCC 15.3:
g++ --help=hardened
salidaThe following options are enabled by -fhardened: -D_FORTIFY_SOURCE=3 (or =2 for glibc < 2.35) -D_GLIBCXX_ASSERTIONS -ftrivial-auto-var-init=zero -fPIE -pie -Wl,-z,now -Wl,-z,relro -fstack-protector-strong -fstack-clash-protection -fcf-protection=full
Casi todas son las mismas que en C (las explicamos en la página de C). La propia de C++ es -D_GLIBCXX_ASSERTIONS, el modo endurecido de libstdc++: con ella, operator[] de vector, string o array, front() en un contenedor vacío y otras operaciones comprueban los límites y paran el programa en lugar de leer o escribir fuera. Cuesta muy poco rendimiento, y por eso varias distribuciones ya compilan así sus paquetes:
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v{10, 20, 30};
std::size_t i = 5;
std::cout << v[i] << '\n';
}g++ -std=c++23 -D_GLIBCXX_ASSERTIONS endurecido.cpp -o endurecido
salida con error/usr/local/include/c++/15.3.0/bits/stl_vector.h:1263: constexpr std::vector< <template-parameter-1-1>, <template-parameter-1-2> >::reference std::vector< <template-parameter-1-1>, <template-parameter-1-2> >::operator[](size_type) [with _Tp = int; _Alloc = std::allocator<int>; reference = int&; size_type = long unsigned int]: Assertion '__n < this->size()' failed.
Sin la opción, ese programa lee lo que haya en memoria después del vector y sigue como si nada: es comportamiento indefinido. libc++, la biblioteca de Clang, tiene lo mismo con -D_LIBCPP_HARDENING_MODE, y C++26 convierte la idea en parte del estándar con su «biblioteca endurecida».
El gran debate: la seguridad de memoria
Los datos de las grandes empresas dicen lo mismo desde hace años. Microsoft calculó en 2019 que alrededor del 70 % de los fallos a los que asigna un CVE cada año son de seguridad de memoria, casi todos en código C y C++. El equipo de Chromium llegó a la misma cifra al analizar 912 fallos graves de Chrome desde 2015, y la mitad eran usos de memoria ya liberada. Uno de ellos, el CVE-2022-0609, en el código de animaciones de Chrome, lo usaron dos grupos de hackers norcoreanos durante seis semanas antes de que Google lo corrigiera, en febrero de 2022.
De ahí salieron las recomendaciones oficiales. La NSA publicó en noviembre de 2022 una guía que pedía pasar de C y C++ a lenguajes con seguridad de memoria siempre que sea posible, y la CISA y el FBI fueron más allá en octubre de 2024: consideran una mala práctica que un fabricante de software no tenga, desde el 1 de enero de 2026, un plan publicado para eliminar estos fallos de sus productos en C y C++ (es una recomendación, no una obligación). Google aporta el dato más citado a favor: al escribir el código nuevo de Android en lenguajes seguros, sobre todo Rust, los fallos de memoria pasaron del 76 % de sus vulnerabilidades en 2019 al 24 % en 2024, sin reescribir el código viejo.
El comité de C++ ha respondido por dos caminos. Uno fue Safe C++, la propuesta de Sean Baxter de 2024 que añadía al lenguaje un comprobador de préstamos como el de Rust; en 2025, el grupo de seguridad del comité votó priorizar la alternativa y la propuesta quedó parada. La alternativa son los perfiles, impulsados por el propio Stroustrup: modos que el compilador aplica a un archivo o a un proyecto y que prohíben las construcciones peligrosas (aritmética de punteros, conversiones sin comprobar) o añaden comprobaciones. Los perfiles no llegaron a tiempo para C++26 y se están trabajando en un documento aparte. Lo que sí trae C++26 son medidas más modestas pero inmediatas: la biblioteca endurecida, los contratos y el fin del comportamiento indefinido al leer variables sin inicializar.
Mientras tanto, la recomendación práctica es la de este curso: C++ moderno (contenedores y punteros inteligentes en lugar de memoria manual), avisos al máximo, sanitizadores en las pruebas, el modo endurecido de la biblioteca en producción y, para el código nuevo donde la seguridad sea crítica, plantearse si Rust u otro lenguaje con seguridad de memoria encaja mejor. Los fallos concretos y cómo evitarlos están en Seguridad en C++.
Los programas y las órdenes de esta página se ejecutaron con GCC 15.3 y Clang 19.1.7 en un contenedor limpio, sin red. Los datos históricos y los de la votación de C++26 se comprobaron en las fuentes del comité y de los propios autores; el registro oficial de ISO de esa votación aún no es público.
