C++ ocupa un lugar particular en la historia de los lenguajes de
programación. Nacido a finales de la década de 1970 en Bell Labs, no
buscaba sustituir radicalmente las herramientas existentes. Su ambición
era más delicada: añadir mecanismos de abstracción potentes sin
renunciar al control, la eficiencia y la proximidad a la máquina que
habían hecho triunfar al lenguaje C.
Esta tensión acompaña a C++ desde sus orígenes.
El lenguaje debe permitir construir grandes programas estructurados y,
al mismo tiempo, seguir siendo adecuado para sistemas en los que
importan el rendimiento, la memoria y los recursos de hardware. Debe
evolucionar sin inutilizar una inmensa cantidad de código existente.
Debe admitir varios estilos de programación sin imponer un único modelo.
Más de cuarenta años después de sus comienzos, C++ sigue utilizándose en
motores de videojuegos, sistemas operativos, navegadores, software
científico, infraestructuras financieras, aplicaciones embebidas y
numerosos componentes en los que el rendimiento es esencial.
Su historia es, por tanto, la de un compromiso permanente entre
abstracción y control.
Antes de C++: el éxito de C
Para comprender C++, primero hay que hablar de C.
A comienzos de la década de 1970, Dennis Ritchie desarrolló C en
Bell Labs en el contexto de la creación de Unix. El lenguaje ofrecía un
equilibrio extraordinario: permitía escribir código relativamente
portable y, al mismo tiempo, proporcionaba al programador un control
preciso sobre la memoria y los recursos de la máquina.
C se convirtió rápidamente en uno de los grandes lenguajes de
programación de sistemas.
Sin embargo, a medida que los programas crecían, aparecía una
dificultad: ¿cómo organizar sistemas complejos sin perder el rendimiento
y el control que ofrecía C?
C permitía estructurar el código mediante funciones, estructuras y
módulos, pero no proporcionaba de forma nativa los mecanismos de
abstracción que algunos lenguajes de investigación ya estaban
explorando.
Ese era precisamente el problema que interesaba a un joven investigador
danés: Bjarne Stroustrup.
Bjarne Stroustrup y la influencia de Simula
A finales de los años setenta, Bjarne Stroustrup trabajaba en su tesis
doctoral en la Universidad de Cambridge.
Entre otros lenguajes, utilizaba Simula, desarrollado en Noruega
durante la década de 1960 y considerado con frecuencia uno de los
principales antecesores de la programación orientada a objetos.
Simula proponía conceptos muy potentes: clases, objetos, herencia y
métodos virtuales.
Para Stroustrup, estas abstracciones facilitaban considerablemente el
modelado de sistemas complejos.
Pero también encontró una limitación: el rendimiento de las herramientas
que utilizaba no respondía a sus necesidades.
Cuando se incorporó a Bell Labs en 1979, trabajó en problemas
relacionados con sistemas distribuidos. Quería disponer de la capacidad
de abstracción de Simula sin perder la eficiencia de C.
La idea fundadora de C++ podría resumirse casi así:
¿y si fuera posible disponer de las clases de Simula sin renunciar al
rendimiento y al ecosistema de C?
La biografía de Bjarne Stroustrup
permite profundizar en su trayectoria y en las decisiones que guiaron el
diseño del lenguaje.
1979: «C with Classes»
El primer proyecto no se llamaba C++.
A partir de 1979, Stroustrup desarrolló un lenguaje denominado C with
Classes, literalmente «C con clases».
El nombre describía muy bien la intención inicial.
No se trataba de empezar desde cero, sino de ampliar C con mecanismos
que permitieran estructurar mejor los programas.
C with Classes introdujo, entre otras cosas:
clases;
constructores y destructores;
control de acceso;
determinadas formas de herencia;
argumentos predeterminados;
funciones integradas en el modelo de clases.
El objetivo ya era ayudar al programador a expresar abstracciones sin
imponer un coste de ejecución innecesario.
Esta idea se convertiría en uno de los principios históricos de C++:
no pagar por aquello que no se utiliza.
Cfront: transformar C++ en C
Los primeros desarrollos de C++ se beneficiaron de una estrategia
pragmática.
En lugar de construir inmediatamente toda una infraestructura de
compilación independiente, Stroustrup desarrolló Cfront.
Cfront traducía el código C with Classes, y posteriormente C++, a código
C.
Después, el resultado podía compilarse mediante los compiladores de C ya
disponibles en diferentes plataformas.
Este enfoque presentaba varias ventajas.
Facilitaba la experimentación.
Permitía al nuevo lenguaje aprovechar el ecosistema existente de C.
También demostraba que un lenguaje con abstracciones más ricas podía
transformarse en código suficientemente próximo a los mecanismos de bajo
nivel.
Cfront desempeñó un papel importante durante los primeros años de C++,
antes de que los compiladores nativos fueran tomando el relevo.
1983: C with Classes se convierte en C++
A comienzos de la década de 1980, el lenguaje evolucionó rápidamente.
En 1983 recibió su nombre definitivo: C++.
El nombre fue propuesto por Rick Mascitti.
En C, el operador ++ incrementa un valor.
«C++» sugiere así, con cierto humor, un C mejorado o incrementado.
El nombre refleja perfectamente la filosofía del proyecto: C++ mantiene
una fuerte continuidad con C y, al mismo tiempo, añade nuevas
capacidades.
Por entonces aparecieron o se reforzaron varios elementos importantes:
funciones virtuales;
sobrecarga de funciones;
sobrecarga de operadores;
referencias;
constantes;
una comprobación de tipos más estricta.
El lenguaje empezaba a adquirir la forma por la que acabaría siendo
reconocible.
1985: la primera difusión comercial
En 1985 apareció la primera edición de The C++ Programming Language,
escrita por Bjarne Stroustrup.
Ese mismo año estuvo disponible la primera versión comercial de C++.
Este periodo marcó la salida del lenguaje del entorno puramente
experimental de Bell Labs.
C++ comenzó a adoptarse en proyectos industriales reales.
Su principal argumento resultaba atractivo: permitir a los
desarrolladores acostumbrados a C construir abstracciones más
estructuradas sin abandonar el control de los recursos.
Esta promesa interesaba especialmente en ámbitos donde el coste de las
abstracciones debía seguir siendo previsible.
La programación orientada a objetos se vuelve central
Durante las décadas de 1980 y 1990, la programación orientada a
objetos experimentó un fuerte crecimiento.
La idea general consistía en organizar los programas alrededor de
objetos que reunían datos y comportamientos.
C++ contribuyó ampliamente a la difusión de este enfoque.
Las clases, la encapsulación, la herencia y el polimorfismo permitían
representar jerarquías y definir interfaces comunes.
Las funciones virtuales desempeñaban, en particular, un papel
esencial.
Permitían seleccionar en tiempo de ejecución la función correspondiente
al tipo real de un objeto.
Esta capacidad hace posible el polimorfismo dinámico.
Pero C++ conservó una filosofía particular: estos mecanismos no eran
obligatorios.
Un programa podía utilizar clases sin herencia, emplear funciones
libres, manipular directamente la memoria o adoptar otros estilos.
Por ello, C++ nunca se convirtió en un lenguaje exclusivamente orientado
a objetos.
Para situar estos conceptos en su contexto histórico, nuestro artículo
sobre la programación orientada a
objetos recorre el camino
desde Simula y Smalltalk hasta los lenguajes modernos.
«Zero-overhead abstractions»
Una idea fue convirtiéndose progresivamente en emblema de la filosofía
de C++: las abstracciones sin sobrecoste impuesto.
El principio puede resumirse así:
lo que no utilizas no debería costarte;
lo que utilizas no debería ser significativamente menos eficiente
que una implementación manual equivalente.
Esta ambición explica muchas de las decisiones del lenguaje.
C++ pretende permitir abstracciones de alto nivel y, al mismo tiempo,
proporcionar al compilador suficiente información para generar código
muy eficiente.
Esto lo diferencia de lenguajes que priorizan un modelo de ejecución más
simple o delegan una mayor parte de la gestión de recursos a una máquina
virtual o a un recolector de basura.
Esta filosofía es extremadamente potente.
También contribuye a la complejidad del lenguaje.
Gestión determinista de los recursos
C++ desarrolló un modelo especialmente importante en torno a los
constructores y destructores.
Cuando se crea un objeto, su constructor puede adquirir un recurso.
Cuando el objeto abandona su ámbito, su destructor puede liberarlo
automáticamente.
Esta idea dio lugar a una práctica conocida como RAII (Resource
Acquisition Is Initialization).
El recurso no tiene por qué ser memoria.
Puede tratarse de:
un archivo;
un bloqueo;
una conexión;
un descriptor del sistema;
un recurso gráfico;
un bloque de memoria.
La ventaja es considerable: la duración de vida de un recurso puede
vincularse a la de un objeto.
Esta gestión determinista sigue siendo hoy una de las características
más importantes del C++ moderno.
Los templates transforman profundamente C++
A finales de los años ochenta y comienzos de los noventa, C++ tomó una
dirección que iba mucho más allá de la orientación a objetos.
Los templates permitieron escribir código genérico.
En lugar de crear una función diferente para cada tipo de datos, el
programador podía definir una operación parametrizada por un tipo.
Por ejemplo, una función genérica podía expresar la idea de buscar un
elemento sin depender directamente de que dicho elemento fuera un
entero, una cadena u otro tipo compatible.
Esta capacidad abrió el camino a la programación genérica.
Acabaría siendo tan importante para la identidad de C++ como las clases
y la herencia.
Fue un giro esencial: reducir C++ a «C con programación orientada a
objetos» resultaba cada vez menos correcto.
El nacimiento de la STL
Uno de los acontecimientos más importantes de la historia del lenguaje
fue la integración de la Standard Template Library, o STL.
La STL procede en gran medida de los trabajos de Alexander Stepanov
y sus colaboradores sobre programación genérica.
Su enfoque se basa en una idea potente: separar las estructuras de datos
de los algoritmos que las manipulan mediante interfaces genéricas.
Así aparecen conceptos que se hicieron familiares para los
desarrolladores de C++:
vector;
list;
map;
iteradores;
algoritmos como la ordenación o la búsqueda.
En lugar de crear una función de ordenación distinta para cada
contenedor, puede definirse un algoritmo genérico que trabaje con
iteradores compatibles.
Este diseño influyó profundamente en C++.
La biblioteca estándar se convirtió progresivamente en una parte
esencial del lenguaje tal como se utiliza realmente.
¿Por qué es tan importante la STL?
La STL demuestra que es posible combinar:
genericidad;
reutilización;
abstracción;
rendimiento.
Una abstracción bien diseñada no implica necesariamente una penalización
importante durante la ejecución.
El compilador puede instanciar y optimizar el código genérico para los
tipos que realmente se utilizan.
Esta filosofía reforzó de manera duradera la cultura de C++.
El lenguaje ya no era simplemente un C enriquecido con clases.
Se convirtió en un entorno multiparadigma, capaz de combinar
programación procedimental, orientada a objetos y genérica.
La normalización se vuelve indispensable
A medida que C++ se difundía, aparecieron múltiples compiladores.
Entonces surgió un problema evidente: sin una norma común, cada
implementación corría el riesgo de desarrollar su propio dialecto.
La estandarización se volvió esencial.
Se puso en marcha un proceso internacional de normalización dentro de
ISO.
El objetivo era definir con precisión el lenguaje y su biblioteca
estándar para que un programa conforme pudiera trasladarse más
fácilmente entre distintas implementaciones.
Este proceso desembocó en 1998 en la primera gran norma internacional de
C++.
C++98: el lenguaje se convierte en un estándar internacional
C++98 constituye una etapa fundamental.
Por primera vez, el lenguaje disponía de una norma ISO completa.
Esta formalizó numerosas funcionalidades desarrolladas durante los años
anteriores e integró la STL en la biblioteca estándar.
El C++ estandarizado contaba ya con una base común para fabricantes de
compiladores y desarrolladores.
Una corrección de la norma, conocida habitualmente como C++03, llegó
algunos años después.
Sin embargo, durante un largo periodo, C++98/03 siguió siendo la
referencia principal.
Esta estabilidad favoreció la adopción industrial.
Pero también tuvo un precio: mientras las prácticas informáticas
evolucionaban rápidamente, el propio lenguaje daba en ocasiones la
impresión de avanzar con lentitud.
Los años 2000: potencia y reputación de complejidad
A comienzos de la década de 2000, C++ estaba enormemente extendido.
Se utilizaba en software de escritorio, videojuegos, sistemas,
aplicaciones financieras, herramientas científicas y numerosas
infraestructuras.
Pero su reputación también se volvió más ambivalente.
El lenguaje acumulaba varias décadas de funcionalidades.
Conservaba una fuerte compatibilidad con C y con código C++ antiguo.
La gestión manual de memoria seguía siendo habitual.
Errores como desbordamientos de búfer, punteros inválidos, dobles
liberaciones o fugas de memoria podían provocar fallos difíciles de
diagnosticar.
Los templates generaban a veces mensajes de error intimidantes.
La sintaxis contenía numerosos casos particulares.
C++ era potente, pero esa potencia exigía una disciplina considerable.
Java propone otra respuesta
En la década de 1990 apareció Java con una filosofía diferente.
El lenguaje adoptó una sintaxis familiar para los desarrolladores de C y
C++, pero eliminó o restringió varios mecanismos considerados peligrosos
o demasiado complejos.
Se apoyó especialmente en una máquina virtual y en un recolector de
basura para automatizar parte de la gestión de memoria.
Esta diferencia ilustra dos enfoques.
C++ favorece intensamente el control, la compatibilidad y la posibilidad
de optimizar cerca de los recursos subyacentes.
Java acepta una mayor abstracción durante la ejecución a cambio de un
entorno más uniforme y una gestión de memoria más automatizada.
Nuestro artículo sobre la historia de Java
permite comparar estas trayectorias.
Los dos lenguajes no responden exactamente a las mismas restricciones, y
su coexistencia demuestra que no existe una única forma de diseñar un
lenguaje de propósito general.
C++11: el renacimiento del lenguaje
La siguiente gran transformación llegó en 2011.
La norma C++11 fue tan importante que suele considerarse el comienzo del
C++ moderno.
Aparecieron o se volvieron realmente prácticas numerosas
funcionalidades:
auto para la inferencia de tipos;
expresiones lambda;
referencias rvalue;
semántica de movimiento;
punteros inteligentes estándar;
nullptr;
bucles for basados en rangos;
mejor soporte para programación concurrente;
constexpr;
listas de inicialización;
templates variádicos.
Esta lista representa solo una parte de los cambios.
C++11 no se limitó a añadir funcionalidades.
Cambió la forma recomendada de escribir C++.
La semántica de movimiento
Entre las innovaciones de C++11, la semántica de movimiento
representa especialmente bien la filosofía del lenguaje.
Imaginemos un objeto que contiene una gran cantidad de datos.
Copiarlo puede resultar costoso.
Pero si el objeto de origen ya no va a utilizarse, puede ser posible
transferir directamente sus recursos al nuevo objeto en lugar de
copiarlo todo.
Las referencias rvalue y las operaciones de movimiento permiten expresar
esta situación.
El lenguaje obtiene así una abstracción más expresiva sin sacrificar
innecesariamente el rendimiento.
Es un excelente ejemplo del objetivo histórico de C++: permitir
abstracciones de alto nivel manteniendo un control preciso sobre su
coste.
Los punteros inteligentes cambian las prácticas
Durante mucho tiempo, aprender C++ significaba a menudo aprender new y
delete desde muy pronto.
El C++ moderno fomenta un enfoque diferente.
Tipos como:
std::unique_ptr;
std::shared_ptr;
std::weak_ptr
permiten expresar de manera más explícita la propiedad y la duración de
vida de los objetos.
Combinados con RAII y con los contenedores de la biblioteca estándar,
reducen considerablemente la necesidad de gestionar directamente la
memoria dinámica en el código de aplicación habitual.
Esto no convierte C++ en un lenguaje con recolector de basura.
La diferencia es importante.
La liberación de recursos suele seguir siendo determinista, pero puede
automatizarse mediante los propios tipos.
C++ moderno: evitar el C++ «histórico»
A partir de C++11 se extendió una idea: el código C++ moderno puede ser
muy diferente del estilo que se enseñaba en la década de 1990.
Las buenas prácticas recomiendan, entre otras cosas:
preferir objetos con duración de vida automática;
utilizar contenedores estándar en lugar de arrays manuales cuando
sea apropiado;
utilizar punteros inteligentes para expresar la propiedad;
evitar asignaciones explícitas innecesarias;
aprovechar algoritmos y abstracciones de la biblioteca estándar;
limitar las conversiones peligrosas;
sacar mayor partido del sistema de tipos.
Esta evolución es importante.
Parte de la reputación de C++ procede de prácticas antiguas que siguen
siendo técnicamente posibles, pero que ya no representan necesariamente
la forma recomendada de escribir programas nuevos.
C++14: consolidar C++11
La norma C++14 fue deliberadamente menos revolucionaria.
Mejoró y completó mecanismos introducidos en 2011.
El objetivo era, entre otras cosas, hacer determinadas funcionalidades
más sencillas y coherentes.
C++ entró entonces en un ritmo de evolución más regular.
En lugar de esperar más de una década entre dos grandes
transformaciones, la comunidad pasó a una cadencia aproximada de tres
años entre nuevas normas.
C++17: enriquecer el lenguaje y su biblioteca
C++17 continuó esta modernización.
Entre sus incorporaciones importantes se encuentran:
std::optional;
std::variant;
std::any;
std::string_view;
structured bindings;
if constexpr;
importantes mejoras de la biblioteca estándar.
Estas funcionalidades permiten expresar directamente situaciones que
antes requerían convenciones o bibliotecas externas.
std::optional, por ejemplo, permite representar explícitamente un
valor que puede estar ausente.
El propio tipo documenta esa posibilidad.
Este enfoque ilustra una tendencia del C++ moderno: utilizar el sistema
de tipos para hacer más explícitas las intenciones del programa.
C++20: concepts, ranges y coroutines
C++20 representa otra etapa importante.
Tres familias de funcionalidades atrajeron especialmente la atención:
los concepts;
los ranges;
las coroutines.
Los concepts permiten expresar con mayor claridad las restricciones
impuestas a los parámetros de templates.
Se trata de una evolución importante para la programación genérica.
Durante mucho tiempo, un error al utilizar un template podía producir
diagnósticos extremadamente complejos.
Los concepts permiten indicar de manera más explícita qué debe ser capaz
de hacer un tipo para ser aceptado.
Los ranges ofrecen una forma más expresiva de componer operaciones sobre
secuencias.
Las coroutines proporcionan mecanismos del lenguaje para suspender y
reanudar la ejecución de una función, útiles especialmente para
determinados modelos asíncronos.
C++20 también introdujo los módulos en el estándar, con la ambición de
ofrecer una alternativa moderna a determinados usos históricos de los
archivos de cabecera.
C++23 y la evolución continua
C++23 prosigue este ciclo de mejora.
Como ocurre a menudo con C++, evolucionar no significa sustituir
bruscamente el lenguaje existente.
Se añaden nuevas funcionalidades mientras la inmensa base de código
histórica sigue siendo importante.
La biblioteca estándar continúa creciendo.
Los mecanismos de programación genérica siguen evolucionando.
El uso de constexpr permite realizar cada vez más cálculos durante la
compilación.
Las herramientas, los compiladores y los analizadores estáticos también
progresan.
Esta evolución incremental es a la vez una fortaleza y una dificultad.
C++ puede seguir modernizando las prácticas.
Pero debe cargar con una historia extraordinariamente larga.
¿Por qué no eliminar simplemente las funcionalidades antiguas?
La pregunta parece natural.
Si algunas construcciones están hoy desaconsejadas, ¿por qué no
retirarlas?
Porque una de las grandes fortalezas de C++ es su inmenso ecosistema
existente.
Las empresas utilizan millones de líneas de código desarrolladas durante
décadas.
Bibliotecas y sistemas críticos deben poder seguir manteniéndose.
Eliminar bruscamente una funcionalidad puede romper una cantidad
considerable de software.
La compatibilidad es, por tanto, una restricción fundamental en la
evolución del lenguaje.
Esta política contribuye a su longevidad.
Pero también explica parte de su complejidad: varias generaciones de
prácticas pueden coexistir dentro del mismo lenguaje.
¿C++ es realmente compatible con C?
La relación entre C y C++ es más sutil que un simple «sí».
C++ fue concebido históricamente como una extensión de C y mantiene una
gran proximidad sintáctica y conceptual con él.
Muchos programas C pueden adaptarse con facilidad.
Los dos lenguajes también comparten convenciones y pueden interactuar
mediante interfaces adecuadas.
Pero C y C++ son hoy dos lenguajes distintos, cada uno con su propia
norma y su propia evolución.
Un programa C válido no es necesariamente un programa C++ válido.
A la inversa, los patrones recomendados en el C++ moderno suelen diferir
considerablemente de los utilizados en C.
Comprender esta distinción evita reducir C++ a «C con algunas
funcionalidades adicionales».
Un lenguaje multiparadigma
A veces se presenta C++ como un lenguaje orientado a objetos.
Esa descripción es incompleta.
El lenguaje permite varios estilos:
programación procedimental;
programación orientada a objetos;
programación genérica;
programación funcional en determinadas construcciones;
metaprogramación en tiempo de compilación;
programación próxima al sistema.
Un mismo programa puede combinar varios de estos enfoques.
Esta flexibilidad es una de las grandes fortalezas de C++.
Permite escoger abstracciones adecuadas para diferentes problemas.
Pero también significa que suele haber varias formas razonables de
realizar una misma tarea.
Metaprogramación: cuando calcula el compilador
Los templates tuvieron un efecto inesperado.
Los desarrolladores descubrieron que podían utilizarlos para realizar
cálculos durante la compilación.
La template metaprogramming se convirtió así en un ámbito propio.
Al principio, algunas de estas posibilidades casi parecían un uso
ingenioso e imprevisto del sistema de templates.
Con las sucesivas versiones, C++ incorporó mecanismos más directos,
especialmente constexpr y posteriormente consteval.
El lenguaje permite ahora ejecutar una cantidad creciente de lógica
durante la compilación.
Esto puede servir para comprobar propiedades, preparar datos o eliminar
determinados costes durante la ejecución.
Una vez más aparece el objetivo de conseguir abstracción sin sacrificar
rendimiento.
¿Por qué se sigue utilizando C++?
Cada vez que aparece un nuevo lenguaje de sistemas, en ocasiones se
anuncia la desaparición de C++.
Sin embargo, sigue estando enormemente presente.
Varias razones explican esta longevidad.
Rendimiento
C++ permite producir código nativo de muy alto rendimiento.
El programador dispone de un control importante sobre las asignaciones,
la disposición de los datos, las copias y los recursos.
Predictibilidad
En muchos sistemas, no basta con ser rápido de media.
También es necesario controlar cuándo se producen determinadas
operaciones costosas.
La gestión determinista de recursos de C++ resulta valiosa en este
contexto.
Ecosistema
Décadas de desarrollo han producido una cantidad inmensa de bibliotecas,
motores, frameworks y bases de código.
Interoperabilidad
C++ puede interactuar estrechamente con C y con numerosas API del
sistema.
Compiladores
GCC, Clang y Microsoft Visual C++ son herramientas extremadamente
maduras y optimizadas.
Portabilidad
C++ está disponible en una enorme variedad de plataformas, desde
microcontroladores hasta servidores de gran potencia.
Videojuegos y motores 3D
Los videojuegos constituyen uno de los ámbitos más visibles en los que
C++ sigue siendo dominante.
Los motores deben gestionar:
renderizado gráfico;
física;
audio;
inteligencia artificial;
red;
memoria;
carga de recursos;
restricciones de tiempo real.
El rendimiento y el control preciso de los recursos son esenciales.
Por ello, muchos grandes motores y videojuegos utilizan C++ de forma
intensiva.
Esto no significa que todo el código de un videojuego deba escribirse en
C++.
Los lenguajes de scripting y las herramientas de mayor nivel suelen
utilizarse como complemento.
Pero C++ permanece con frecuencia en el núcleo de los componentes más
exigentes.
Navegadores y software complejo
Los navegadores modernos ofrecen otro buen ejemplo.
Mostrar una página web implica motores de renderizado, motores
JavaScript, red, seguridad, multimedia, gráficos y una enorme cantidad
de lógica.
Muchos componentes de los navegadores se han escrito históricamente en
C++.
El lenguaje también se utiliza en software de creación gráfica,
herramientas de diseño, bases de datos y aplicaciones científicas.
Su terreno natural sigue siendo a menudo aquel en el que se combinan
software de gran tamaño y fuertes exigencias de rendimiento.
Sistemas embebidos y restricciones de hardware
C++ también está muy presente en los sistemas embebidos.
En estos entornos, la memoria puede ser limitada.
El consumo energético puede ser importante.
El hardware puede imponer restricciones precisas.
Algunas aplicaciones no pueden depender de una máquina virtual pesada ni
de un recolector de basura cuyas pausas sean difíciles de predecir.
C++ permite utilizar abstracciones modernas manteniendo un control
próximo al hardware.
Naturalmente, no todas las funcionalidades del lenguaje se utilizan en
todos los sistemas embebidos.
Es habitual emplear subconjuntos y reglas estrictas de desarrollo.
Finanzas, cálculo científico e infraestructuras
En las finanzas de baja latencia, unos pocos microsegundos pueden
importar.
En el cálculo científico, grandes cantidades de datos deben procesarse
con eficiencia.
En determinadas infraestructuras, el consumo de CPU y memoria a gran
escala se convierte en una cuestión económica.
Estas restricciones siguen creando un espacio importante para C++.
El lenguaje permite construir abstracciones sofisticadas y, al mismo
tiempo, acceder a los detalles que se vuelven importantes cuando el
rendimiento alcanza sus límites.
El coste de esta potencia: la complejidad
La longevidad de C++ tiene una contrapartida.
El lenguaje es enorme.
Contiene funcionalidades heredadas de distintas épocas.
Algunas construcciones interactúan de manera sutil.
Las reglas sobre duración de vida, sobrecarga, resolución de templates o
conversiones pueden volverse complejas.
Un principiante puede escribir rápidamente un pequeño programa en C++.
Dominar realmente el lenguaje requiere mucho más tiempo.
Esta dificultad no es simplemente un defecto accidental.
Procede en parte de los objetivos contradictorios perseguidos por C++:
seguir siendo eficiente;
conservar la compatibilidad;
ofrecer abstracciones potentes;
funcionar en numerosas plataformas;
admitir varios estilos de programación;
evolucionar sin romper el software existente.
La seguridad de memoria se convierte en un reto fundamental
Una parte importante de las vulnerabilidades del software de sistemas ha
procedido históricamente de errores de gestión de memoria.
C++ permite escribir código seguro y estructurado, especialmente gracias
a RAII, los contenedores estándar y las prácticas modernas.
Pero también sigue permitiendo operaciones peligrosas de bajo nivel
cuando el programador las solicita.
Esta capacidad es útil en determinados ámbitos.
También constituye un riesgo.
Los debates actuales sobre los lenguajes denominados memory-safe han
reavivado por ello la reflexión sobre el lugar de C++.
Lenguajes más recientes intentan ofrecer un rendimiento de sistemas
comparable y, al mismo tiempo, impedir más errores de memoria desde la
compilación.
Esta competencia también impulsa al ecosistema C++ a mejorar sus
herramientas, recomendaciones y mecanismos de seguridad.
C++ frente a lenguajes más recientes
C++ ha visto aparecer varias generaciones de competidores.
Java propuso portabilidad y gestión automatizada de memoria.
C# desarrolló un entorno moderno alrededor de .NET.
Go priorizó la simplicidad y la concurrencia.
Rust intenta combinar rendimiento de sistemas con fuertes garantías de
seguridad de memoria.
Otros lenguajes siguen explorando nuevos enfoques.
Sin embargo, sustituir C++ no es únicamente una cuestión de calidad
sintáctica.
También hay que considerar:
las bases de código existentes;
las bibliotecas;
las herramientas;
las competencias disponibles;
las plataformas compatibles;
las restricciones de migración;
la interoperabilidad con sistemas ya en producción.
Por ello, la evolución de los lenguajes suele producirse mucho más por
coexistencia que por sustitución instantánea.
¿Por qué C++ no se ha rediseñado completamente?
Un lenguaje nuevo podría ser más sencillo si abandonara toda
compatibilidad.
Pero ya no sería C++.
El proyecto C++ se basa históricamente en una evolución continua.
Esta continuidad permite que el código antiguo sobreviva y que sistemas
gigantescos migren de manera gradual.
El precio es una complejidad acumulada.
C++ vive así con una contradicción estructural:
para seguir siendo moderno debe evolucionar; para conservar su valor
industrial no puede olvidar demasiado deprisa su pasado.
Una gran parte del trabajo del comité de estandarización consiste
precisamente en gestionar esta tensión.
El papel del comité ISO
Desde su estandarización, C++ ya no es el proyecto de una sola persona.
Bjarne Stroustrup sigue siendo una figura fundamental de su historia y
continúa participando en los debates, pero la evolución del lenguaje es
colectiva.
Representantes de empresas, de la investigación, de fabricantes de
compiladores y de la comunidad proponen y evalúan cambios.
Las decisiones deben tener en cuenta numerosos factores:
utilidad;
rendimiento;
compatibilidad;
posibilidad de implementación;
coherencia con el resto del lenguaje;
experiencia de los desarrolladores.
Esta gobernanza explica en ocasiones la lentitud de ciertas evoluciones.
Pero también es necesaria para un lenguaje utilizado en sistemas
críticos a escala mundial.
C++ ya no es el lenguaje de 1985
Comparar el C++ actual con su primera versión muestra una transformación
considerable.
El lenguaje de 1985 estaba fuertemente centrado en añadir mecanismos
orientados a objetos a C.
El C++ moderno dispone de:
una amplia biblioteca estándar;
contenedores genéricos;
algoritmos;
lambdas;
herramientas de gestión automática de recursos;
concurrencia estandarizada;
mecanismos de cálculo en tiempo de compilación;
concepts;
ranges;
coroutines;
módulos;
un sistema de templates extremadamente potente.
Sin embargo, sigue siendo reconocible una línea directriz:
proporcionar abstracciones útiles sin quitar al programador la
posibilidad de controlar su coste.
C++ y la idea de «lenguaje de propósito general»
C++ no es únicamente un lenguaje de sistemas.
Se ha utilizado para construir prácticamente todos los tipos de
software.
Pero no pretende necesariamente ser el lenguaje más sencillo para cada
tarea.
Para un pequeño script, Python puede permitir escribir el programa más
rápidamente.
Para determinadas aplicaciones web, otros ecosistemas resultan más
naturales.
Para algunos nuevos proyectos de sistemas, Rust puede aportar garantías
interesantes.
La fortaleza de C++ se encuentra en otro lugar.
Ofrece una combinación poco frecuente de:
rendimiento;
control;
abstracción;
portabilidad;
madurez;
compatibilidad con un inmenso patrimonio de software.
Esta combinación explica su permanencia.
Un lenguaje moldeado por sus restricciones
La historia de C++ se cuenta a veces como una acumulación de
funcionalidades.
Esta interpretación deja escapar lo esencial.
Muchas de sus características se explican por las restricciones que el
lenguaje intenta respetar simultáneamente.
Quiere ofrecer abstracciones más ricas que C.
Quiere seguir siendo utilizable en programas donde el rendimiento
importa.
Quiere funcionar sin imponer un entorno de ejecución pesado.
Quiere conservar una gran compatibilidad con décadas de código.
Quiere evolucionar junto con las prácticas modernas.
Estos objetivos no siempre son compatibles.
La complejidad de C++ es, en parte, el resultado de esta negociación
permanente.
De C with Classes al C++ moderno
El camino recorrido desde 1979 es considerable.
Al principio, Stroustrup buscaba sobre todo añadir a C mecanismos
inspirados en Simula.
Después llegaron las funciones virtuales, la sobrecarga y la herencia.
Los templates hicieron emerger la programación genérica.
La STL transformó la biblioteca estándar.
C++98 estabilizó el lenguaje a escala internacional.
C++11 renovó profundamente las prácticas.
C++14, C++17, C++20 y C++23 establecieron un ritmo regular de evolución.
El lenguaje cambia, pero nunca rompe por completo con su historia.
Quizá sea esa su característica más notable.
Lo esencial
C++ nació en 1979, cuando Bjarne Stroustrup intentó combinar las
abstracciones de Simula con la eficiencia y el ecosistema del lenguaje
C.
Inicialmente denominado C with Classes, el proyecto se convirtió en
C++ en 1983.
Sin embargo, su historia no se limita a la programación orientada a
objetos.
Los templates y la STL convirtieron la programación genérica en
uno de los pilares esenciales del lenguaje.
La estandarización C++98 estableció una norma internacional,
mientras que C++11 abrió la era del C++ moderno con lambdas,
semántica de movimiento, punteros inteligentes y muchas otras mejoras.
Las versiones posteriores continuaron esta modernización con concepts,
ranges, coroutines, módulos y una biblioteca estándar cada vez más rica.
C++ conserva hoy un lugar importante porque combina propiedades
difíciles de reunir:
abstracción, rendimiento, control de los recursos, portabilidad y
compatibilidad con varias décadas de software.
Esta potencia tiene un coste: el lenguaje es complejo y conserva
mecanismos históricos que las prácticas modernas intentan evitar con
frecuencia.
Pero esa misma continuidad también explica su longevidad.
Más de cuarenta años después de C with Classes, C++ sigue siendo fiel al
problema inicial de Stroustrup: ¿cómo construir software de alto nivel
sin perder innecesariamente el control de la máquina?
Preguntas frecuentes
¿Quién creó C++?
C++ fue creado por Bjarne Stroustrup en Bell Labs a partir de 1979.
¿Cómo se llamaba originalmente C++?
El proyecto se llamaba inicialmente C with Classes. Su objetivo era
añadir a C mecanismos inspirados especialmente en Simula, en particular
las clases.
¿Por qué el lenguaje se llama C++?
En C, el operador ++ incrementa un valor. El nombre C++ sugiere así un
«C incrementado». Se adoptó en 1983.
¿C++ es simplemente una versión orientada a objetos de C?
No. La orientación a objetos desempeñó un papel importante en sus
comienzos, pero C++ es hoy un lenguaje multiparadigma que también admite
programación procedimental, genérica, metaprogramación y otros estilos.
¿Cuál es la diferencia entre C y C++?
C y C++ son hoy dos lenguajes distintos. Comparten una historia, gran
parte de su sintaxis y numerosos conceptos, pero cada uno posee su
propia norma y sus propias prácticas.
¿Por qué es tan importante C++11?
C++11 modernizó profundamente el lenguaje con las lambdas, auto, la
semántica de movimiento, los punteros inteligentes, nullptr,
constexpr, la concurrencia estandarizada y numerosas mejoras del
sistema de tipos.
¿C++ utiliza un recolector de basura?
C++ no depende normalmente de un garbage collector obligatorio. Favorece
especialmente la gestión determinista de recursos mediante RAII, los
contenedores y los punteros inteligentes.
¿Se sigue utilizando C++ hoy?
Sí. Sigue muy presente en videojuegos, motores 3D, navegadores, sistemas
embebidos, software científico, infraestructuras financieras y numerosas
aplicaciones que requieren un alto rendimiento.
¿Por qué se considera difícil C++?
Su larga historia, sus requisitos de compatibilidad, sus múltiples
paradigmas y el nivel de control que ofrece han dado lugar a un lenguaje
muy amplio. El C++ moderno permite evitar muchas prácticas antiguas,
pero dominarlo en profundidad sigue siendo exigente.
¿C++ será sustituido?
Los lenguajes nuevos responden a algunas limitaciones de C++,
especialmente en seguridad de memoria o simplicidad. Sin embargo, su
enorme ecosistema, su rendimiento, su portabilidad y las bases de código
existentes hacen poco probable una sustitución completa a corto plazo.
La evolución se producirá más probablemente mediante coexistencia y
migraciones progresivas.