La biblioteca estándar de C++ lleva 15 años retractándose de sí misma, y las pruebas están a la vista
(hftuniversity.com)- La biblioteca estándar de C++ ha repetido, desde C++11, el patrón de retirar formalmente diseños incorrectos o dejarlos abandonados junto a reemplazos nuevos, creando una estructura donde el desarrollador debe saber en qué momento empieza la capa de “no deberías usar esto”
- La capa de retiro oficial incluye elementos con propuestas de deprecación o eliminación como
std::auto_ptr, las especificaciones dinámicas de excepciones, la interfaz de recolección de basura de C++11 ystd::aligned_storage;std::functiontambién queda dentro de un flujo de reemplazo de 15 años rodeado porstd::move_only_function,std::copyable_functionystd::function_ref - La capa de evasión informal incluye
std::regex, que es lento;std::async, cuyo destructor espera y crea trampas de interbloqueo; y funciones como<iostream>,std::list,std::dequeystd::vector<bool>, que siguen en el estándar pero el código de producción suele esquivar - El problema de los contenedores por defecto se nota especialmente en
std::unordered_map,std::mapystd::list; en un benchmark con la misma carga, el P99 de una implementación ingenua en C++ fue de 302,653 cycles, mientras que la implementación ingenua en Rust marcó 5,177 cycles, una diferencia de 58x - La elección de estabilidad ABI es la diferencia clave: mientras otros lenguajes reducen errores mediante eliminaciones, ediciones o cambios de versión mayor, C++ conserva de hecho dentro de
std::sus valores por defecto equivocados de forma casi permanente
Punto de partida: el veredicto de “legacy” sobre std::function
- La tabla de referencia rápida de Sandor Dargo para
std::copyable_functionclasifica astd::functioncomo “Legacy. Avoid in new code.” std::functionllegó en C++11 y su reemplazo envoltorio más reciente,std::copyable_function, llega en C++26; la recomendación del nuevo recurso se parece menos a “úsalo cuando necesites un objeto invocable copiable” y más a “no uses el original”- El
const operator()destd::functiontiene un defecto de const-correctness porque invoca objetos invocables non-const, y ya no puede corregirse sin romper el ABI - Como respuesta a ese defecto,
std::move_only_functionqueda en la línea de P0288R9 de C++23,std::copyable_functionen P2548R6 de C++26 ystd::function_refen P0792R14 de C++26
Funciones del estándar que se revirtieron oficialmente
std::auto_ptrfue marcado para deprecación en C++11 porque su semántica de copia-movimiento rompía el código genérico y los contenedores estándar, y fue eliminado en C++17 mediante N4190; el mismo documento también eliminó los adaptadores de<functional>de C++98 ystd::random_shufflestd::random_shufflefue reemplazado porstd::shuffleporque dependía destd::randy de estado global- Las especificaciones dinámicas de excepciones
throw(X, Y)quedaron deprecadas en C++11, se eliminaron en C++17 con P0003R5, y el aliasthrow()se eliminó en C++20 mediante P1152 std::iteratorquedó deprecado en C++17 mediante P0174R2 y su eliminación en C++26 se impulsa en P3365R1; la alternativa es definir directamente los cinco typedefsstd::aligned_storageystd::aligned_unionllegaron en C++11, pero quedaron deprecados en C++23 mediante P1413R3; se señalan como problemas el boilerplate detypename ::type, el uso dereinterpret_cast, el comportamiento indefinido deLen == 0y la falta de constexprstd::not1,std::not2,unary_negateybinary_negatequedaron deprecados en C++17 y se eliminaron en C++20, reemplazados porstd::not_fnde P0005- La interfaz de recolección de basura de C++11, de la familia
std::declare_reachable, fue eliminada en C++23 por P2186R2 porque las implementaciones principales nunca ofrecieron un recolector de basura real - Concepts TS, Modules TS, Coroutines TS, Reflection TS, Executors TS y Networking TS también pasaron por rediseños, reemplazos o demoras antes de integrarse; Reflection ahora apunta a P2996 y Executors cambió hacia el modelo sender/receiver de P2300
Funciones que siguen en el estándar pero se evitan en la práctica
std::regexllegó en C++11, pero P1844R1 dejó asentado en el comité que “funciona muy mal en comparación con otras soluciones disponibles”; la línea de reemplazo apunta a CTRE y P1433R0, y fuera del estándar a Boost.Regex, RE2 y PCRE2std::asyncbloquea en el destructor del future devuelto hasta que termine la tarea asíncrona, y N3679 documenta las trampas de interbloqueo que eso provoca<iostream>es lento, está atado a locale, no es thread-safe para formateo y tiene mensajes de error de mala fama, pero ni siquiera con la llegada destd::formaten C++20 mediante P0645 ni destd::printystd::printlnen C++23 mediante P2093 quedó deprecadostd::listes uno de los casos donde Bjarne Stroustrup mostró en su keynote de GoingNative 2012 que incluso en cargas con inserciones intermedias ganabastd::vector; su texto posterior Are lists evil? responde casi que sístd::dequees un caso donde el issue público de Microsoft STL microsoft/STL#147 registra que el tamaño de bloque exigido por el estándar es demasiado pequeño y que el próximo ABI break necesitaría una gran reforma de rendimientostd::valarrayentró en 1998 como contenedor numérico, pero la optimización por expression templates nunca se materializó y, según cppreference, las implementaciones no parecen tener código especial más allá del de un contenedor comúnstd::vector<bool>tiene como análisis de referencia Onvector<bool>de Howard Hinnant; el almacenamiento bit-packed en sí es útil, pero el problema es estar nombrado como una especialización destd::vector, lo que crea trampas en código genérico cuandoT = boolvolatilequedó deprecado en operaciones compuestas y en posiciones de parámetro y retorno con P1152R4 en C++20, luego parte de eso se revirtió en C++23 con P2327R1, y en C++26 se prevé otra reversión adicional en P2866R0
Contenedores por defecto que no pueden corregirse por ABI
std::unordered_maptiene una especificación de C++11 sobre buckets y estabilidad de iteradores que en la práctica prohíbe open addressing, y se presenta la estructura SwissTable de Google con una ventaja aproximada de 3x sobrestd::unordered_map- Folly F14, Boost
unordered_flat_mapyankerl::unordered_denseson reemplazos en la misma dirección; elHashMapde Rust usa como valor por defecto de la biblioteca estándar un port de SwissTable vía hashbrown std::mapystd::setson árboles red-black basados en nodos, así que requieren una asignación en heap por nodo y persiguen punteros en cada recorrido; Abseilbtree_mapy RustBTreeMapevitan ese problema con base en B-tree- C++23 añadió
std::flat_mapystd::flat_setcon P0429R9, pero no puede cambiar el diseño por defecto destd::unordered_map,std::mapnistd::list - El benchmark multi-symbol order book benchmark compara, con la misma carga, la misma seed y el mismo núcleo aislado,
std::unordered_map+std::map+std::listen C++ contraHashMap+BTreeMap+VecDequeen Rust
| Implementación | P99 cycles |
|---|---|
C++ naive (unordered_map + map + list) |
302,653 |
C++ step 1 (flat_hash_map + map + deque) |
9,951 |
C++ step 2 (flat_hash_map + btree_map + deque) |
9,114 |
C++ step 3 (flat_hash_map + btree_map + vector) |
4,268 |
Rust naive (HashMap + BTreeMap + VecDeque) |
5,177 |
- Cambiar
std::listporstd::vectorda por sí solo cerca de 70x; pasar destd::unordered_mapaflat_hash_mapda entre 3x y 5x; y pasar destd::mapabtree_mapda 1.09x, un efecto dentro del ruido - El punto de la comparación no es que el lenguaje Rust sea 58 veces más rápido que C++, sino que la biblioteca estándar de Rust eligió mejores valores por defecto y la de C++ no puede corregir esos tres valores por defecto por culpa del ABI
El problema Vasa y la acumulación de funciones
- En el documento de WG21 de 2018 P0977R0 “Remember the Vasa!”, Bjarne Stroustrup usa el hundimiento del buque de guerra sueco Vasa de 1628 como metáfora y diagnostica que el comité tiene “unos 150 cocineros” y no atiende suficientemente cómo afecta cada función al sistema completo
std::simdaparece en std::simd Is a Solution to the Wrong Problem como un caso representativo del mismo patrón; es una función impulsada por Matthias Kretz desde la biblioteca Vc, pasando por P0214, Parallelism TS 2 y P1928 hasta llegar a C++26- Cuando
std::simdentra al estándar, fuera de él ya existían Google Highway, ISPC, EVE, xsimd y SIMDe, y también habían mejorado los auto-vectorizers de GCC y Clang, al punto de que se presenta un resultado donde un loop escalar con-O3 -march=nativesupera astd::simd std::simdcompila 10 veces más lento que el código escalar equivalente, rinde peor que el auto-vectorizer al que pretendía reemplazar y no puede expresar vectores de ancho escalable de ARM SVE ni runtime dispatch- Las tres implementaciones libstdc++, libc++ y MSVC STL son mantenidas por equipos pequeños, de un solo dígito de ingenieros cada una; cada nueva función estándar amplía la matriz de pruebas, los bugs de conformidad, las interacciones entre funciones y las entradas del bug tracker que heredará el siguiente mantenedor
std::regexarrastra problemas conocidos desde hace 15 años,std::dequetiene un issue abierto que apunta a rediseño, y los modules de C++20 se describen como algo que seis años después de su estandarización todavía no funciona limpiamente en las tres implementaciones- El conocimiento operativo real del C++ moderno queda concentrado en un pequeño grupo de expertos de tiempo completo que aprendieron el calendario de las capas equivocadas, los rodeos con librerías de terceros, las diferencias entre las tres implementaciones de la biblioteca estándar y la distancia entre teoría y práctica
Diferencia frente a otros lenguajes: no si hubo errores, sino cuánto se conservan
- Python eliminó más de 20 módulos de su biblioteca estándar mediante PEP 594, quitó
distutilsen Python 3.12 con PEP 632, y PEP 387 explicita que puede acortarse el ciclo de retiro de funciones peligrosas o rotas - Java marcó la Applet API para eliminación futura en Java 9, la dejó programada para eliminar en Java 17 y completó la eliminación real en una ruta de ocho años con JEP 504; Nashorn se eliminó en Java 15 mediante JEP 372
- Java SecurityManager quedó en estado de deprecación para eliminación con JEP 411 y se desactivó permanentemente con JEP 486; JEP 398 trata la ruta de eliminación de la Applet API
- Rust elige sus ediciones 2015, 2018, 2021 y 2024 por crate mediante opt-in en
Cargo.toml;mem::uninitializedfue reemplazado porMaybeUninit,std::error::Error::descriptionporsourcey el macrotry!por el operador? - C# aceptó cambios de versión mayor al pasar de .NET Framework a .NET Core, dejando atrás BinaryFormatter, AppDomains, Remoting, Code Access Security, WCF server y WebForms
- JavaScript casi no elimina nada por las restricciones de compatibilidad web, pero las cancelable promises fueron retiradas en Stage 1 y SIMD.js se abandonó en favor de WebAssembly SIMD; Go, por su promesa de compatibilidad Go 1, optó por dejar
io/ioutilsolo como deprecado - La diferencia de C++ no es que haya cometido errores, sino la tasa de conservación con la que casi nunca puede sacar cosas como
std::regex,std::unordered_map,std::vector<bool>,std::valarrayo el defecto de const-correctness destd::function
La estabilidad ABI crea conservación permanente
- P1863R1 “ABI - Now or Never” planteó si C++23 debía aceptar un ABI break o elegir estabilidad ABI permanente, y el comité en la práctica eligió la estabilidad ABI permanente
- Esa decisión vuelve difíciles arreglos como corregir
std::regex, moverstd::unordered_mapa open addressing o cambiar la estructura destd::list,std::mapystd::deque - El ABI de la biblioteca estándar de C++ lo impone el dynamic linker, porque objetos compilados con una versión de libstdc++ deben poder enlazarse con objetos de otra versión, así que detalles como el layout de
std::stringo la configuración destd::regex_traitsquedan fijados en los binarios distribuidos - Esa restricción se concreta en documentos como la política ABI de libstdc++ y el Itanium C++ ABI
- Un usuario de Python elige
python==3.12, uno de Rust elige la edición enCargo.toml, uno de Java elige la versión de JDK y uno de C# un TFM comonet6.0onet8.0, pero C++ no tiene unCargo.tomlparastd:: -std=c++26elige qué headers y reglas del lenguaje usar, pero no ofrece otrostd::stringni unstd::unordered_maprediseñado- Por eso, la biblioteca estándar de C++ que llega a producción en 2026 sigue cargando, por diseño y por fuerza estructural, con los valores por defecto equivocados que el comité aceptó desde 1998
- Los codebases modernos de C++ en firmas de trading tier-one, motores de búsqueda y navegadores dependen fuertemente de librerías no estándar como Boost, Abseil, Folly, EASTL, Chromium
//base, contenedores propios, allocators personalizados, CTRE, Outcome y librerías de coroutines
2 comentarios
El texto original es bastante largo y denso, pero al leerlo hasta el final sí da bastante la sensación de ser devoción por Rust.
Aun así, aprendí mucha información que no conocía. Gracias por el buen artículo.
Opiniones de Lobste.rs
Si pienso en si hubo un churn parecido en el ecosistema de Rust, parece que solo hubo unos pocos casos grandes
En la época de Leakpocalypse, se concluyó que no se podía depender de que el destructor
Dropsiempre se ejecutara para mantener invariantes de seguridad, y en la práctica casi no hubo cambios de API; más o menos solo se eliminóstd::thread::scoped. Después apareció un reemplazo que permite hacer lo mismo de forma soundstd::mem::uninitializedquedó deprecated y ahora se considera unsound. Los tiposRangeexistentes serán reemplazados lentamente por tipos nuevos casi iguales para corregir problemas de API relativamente menores.std::error::Error::descriptionquedó deprecated porque la mayoría de los tipos de error no quieren almacenar una cadena, y existe un reemplazo directo en la implementación deDisplaySi consideras que
stdse ha mantenido estable durante 11 años, es bastante sorprendente, y el resto destdsigue existiendo y funcionando, y el 98% todavía se considera Rust idiomático. En cambio, la biblioteca estándar de C++ parece estar en una posición peligrosa: demasiado ligera de gatillo para agregar funcionalidades y, al mismo tiempo, sorprendentemente conservadora con deprecate en cualquier situaciónIteratortoma prestado su propio contenido. Es un problema crónico que sigue apareciendo en conversaciones sobre Rust como “por qué esto no se puede usar y hay que buscar una solución alternativa”Del mismo modo, el hecho de que
f32yf64no implementenCmpy en su lugar tengan el métodof32::total_cmptambién es una molestia con la que los ingenieros nuevos se topan seguido, así que uno termina suspirando y explicando el contextoEl mecanismo de formateo de panic tampoco es muy bueno, y hay muchos posts de blog sobre cómo el panic handler por defecto usa formateo y, como es difícil desactivarlo, termina inflando bastante el tamaño del ejecutable
Personalmente creo que el diseño anticuado de la biblioteca estándar reduce muchísimo la popularidad y usabilidad de C++
Muchos problemas que suelen atribuirse al lenguaje en sí en realidad deberían apuntar a la biblioteca estándar
Por ejemplo, decir “C++ compila lento” no es exacto. No es que usar características de C++ sea inherentemente lento; lo que lo vuelve lento es la enorme hinchazón de headers y dependencias, y una biblioteca estándar que usa plantillas en exceso incluso para abstracciones simples
“C++ no es seguro” también es cierto en parte, pero el diseño de la biblioteca estándar lo empeora aún más. No hay ninguna razón por la que no se puedan aplicar a una nueva biblioteca estándar los patrones más seguros que se usan en el diseño de APIs de Rust. Claro, una de las grandes ventajas de C++ es la compatibilidad hacia atrás, así que es un problema muy complejo
vec[idx]lance una excepción o haga abort en lugar de provocar acceso fuera de rango/comportamiento indefinido. Pero también hay muchos casos en los que, por diferencias del lenguaje, es mucho más difícil crear APIs seguras en C++Rust tiene por defecto movimiento destructivo, pero C++ no. Por eso las APIs de smart pointers terminan siendo unsafe o, como mínimo, sorprendentes y propensas a crashes. Por ejemplo, haciendo que el programa haga abort si se accede a un smart pointer movido
Rust tiene anotaciones de vida, pero C++ no. Por eso Rust puede evitar cosas como la invalidación de iteradores en el diseño de APIs de iteración, mientras que en C++ eso en realidad es muy difícil. Rust tiene pattern matching, así que APIs como
Optionpueden ofrecer de forma ergonómica un enfoque de “verificar y usar enseguida”. C++ también podría ofrecer una versión destd::optiondonde acceder a un valor vacío no produzca UB, pero sería mucho más incómoda de usar que el C++ actual o que Rust. El operador?de Rust también ayuda mucho aquíSé que a C++ se le puede añadir algo parecido al pattern matching mediante overload sets como en
std::variant, pero me parece bastante más difícil de usar y más fácil de equivocarseCon solo tener una biblioteca moderna de strings y arrays, algunos contenedores genéricos y soporte nativo para allocators, C sería muchísimo más ergonómico y más fácil de usar. Claro, algunas deficiencias del lenguaje no desaparecen solo cambiando la biblioteca, pero aun así se puede llegar bastante lejos
Si miras codebases modernas en C, usan ampliamente bibliotecas personalizadas para allocators, strings, vectors, hash tables y operaciones de sistema de archivos, y si tienes experiencia con C o con administración manual de recursos, no es tan difícil seguir ese camino
slice<T, N>que puede representar un “puntero que apunta exactamente a N bytes” o un “puntero que apunta a una cantidad arbitraria de bytes”Tiene
head(n),tail(n),slice(start, end)y operador de índice, y todos hacen verificación de límitesRealmente da gusto trabajar con este tipo de abstracciones, pero para obtener un lenguaje moderno y razonablemente seguro, en la práctica hay que portar a C++ las bibliotecas estándar de Rust y Zig. Aun así, al final vale la pena por el esfuerzo invertido
Si van a escribir un texto así, por favor escríbanlo ustedes mismos. Aunque tal vez hayan hecho la lista a mano, meterla en un LLM y volcar el resultado en una página web para que la lea la gente me parece demasiado irrespetuoso. Si vuelvo a ver una sola vez más una frase como que a un “ingeniero en activo” le enseñan desde el “primer día” a evitar la “función X”, siento que voy a explotar
Lo vergonzoso es que aquí realmente hay mucho que decir y, sin embargo, no se está diciendo nada. Debe haber una razón por la que se hizo este texto, así que me gustaría que dijeran esa razón. Alguna parte de C++ debe haberte molestado, y alguna funcionalidad te debe haber confundido. Estas funcionalidades son malas no solo por fallas objetivas de diseño, sino por el efecto que tienen sobre nosotros
Si alguna vez usaste
std::iteratory te destrozaron en Slack por eso, o si alguna vez no hiciste un cast porquereinterpret_casttiene 16 letras y arruinaba un poco el formato de la línea, me habría gustado más ver ese tipo de cosas en Lobsters. Si no tienes ese tipo de historias, no las inventes a la fuerza ni hagas que una GPU produzca la misma frase 10 veces con multiplicación de matrices. Solo anota comentarios donde realmente haya algo que comentar, y el resto escríbelo como tablas y bullet pointsLlevo 20 años usando C++ y todavía lo uso, pero coincido mucho con este texto. Hoy en día, lo realmente bueno de usar Rust no es la seguridad de memoria, sino su excelente biblioteca estándar y ecosistema de paquetes
Un ejemplo representativo es la biblioteca de ranges. Han pasado 6 años desde que se estandarizó y las principales bibliotecas estándar todavía no han logrado implementarla por completo; e incluso cuando está implementada, solo hay unos pocos combinadores. Su equivalente en Rust, los métodos de
Iterator, son 76, y con un solocargo addaparecen 130 más con el traititertoolsOtra cosa que de verdad extraño es el pattern matching. Permite hacer ergonómicos los tipos union como
std::variant. La propuesta sigue en discusión, pero todavía no entró ni en C++26, y eso es una lástima. En cambio, sí entran contracts y executors, y sinceramente nunca he visto a nadie a mi alrededor pedirlosEn general, el criterio que yo uso es este: si una funcionalidad soporta casos de uso deseables y no puede expresarse con la biblioteca estándar, entonces debería entrar al lenguaje. Si es posible, conviene descomponer la funcionalidad deseada en los elementos independientes mínimos que también puedan usarse para otros fines
Las funcionalidades que se usan en casi todos los codebases deberían ir en la biblioteca estándar. Si un tipo suele usarse como interfaz entre bibliotecas, debería ir en la biblioteca estándar. No queremos que cada biblioteca defina su propio tipo tuple o su propia cadena. En C++, con lo primero, antes de C++11 eso era prácticamente así; y con lo segundo, como
std::stringes un disaster, en cierto modo todavía lo es. Esto también aplica a los tipos de interfaz, y hoy en día C++ lo resuelve mayormente con conceptsEl resto debería ir en bibliotecas modulares y reutilizables. Rust es bastante bueno para tener un conjunto de bibliotecas externas estables y blessed, así que la presión de decir “todo juego hecho en Rust necesita esta estructura de datos, así que metámosla en la biblioteca estándar” es mucho menor. Quien hace juegos simplemente trae el crate que necesita. C++ nunca ha aceptado del todo la idea de “un buen paquete recomendado para un problema que tiene mucha gente, pero no la mayoría”
Lo preocupante es cuáles de las cosas que se están agregando ahora terminarán revirtiéndose después. Contracts acaba de entrar en C++26 y ya se están señalando fallas graves de diseño
No quiero atacar en general el “diseño por comité”. Creo que estos organismos cumplen un propósito importante y tienen fortalezas propias. Solo que esa fortaleza no está en diseñar funcionalidades completamente nuevas desde cero en un terreno virgen
Donde WG21 y WG14 realmente brillan es en tomar funcionalidades cuyo espacio de diseño ya fue explorado hasta cierto punto y, si es posible, con varias implementaciones existentes, para convertirlas en funcionalidades estándar que la mayoría de usuarios e implementadores puedan aceptar.
std::embedes un ejemplo de esoEn cambio, cuando se estandariza antes de que alguien logre implementarlo bien, como pasó con la extensión de GC mencionada en el artículo,
std::memory_order_consumeo los modules de C++20, las cosas tienden a salir realmente malHace tiempo me impactó bastante darme cuenta de que C++ no versiona su biblioteca estándar. No esperaba que este artículo señalara justo ese punto.
También me pareció interesante que mencionaran que Go es igual de conservador con la compatibilidad hacia adelante. Pero Go también es igual de conservador al agregar funciones nuevas, así que parece haber evitado la mayoría de los problemas de C++. Probablemente también ayudó no tener un ABI estable
De las bibliotecas populares que conozco, la única que expone explícitamente un ABI de C++ es libcamera, y es bastante molesto. En mi experiencia, incluso las bibliotecas de C++ suelen exportar símbolos con ABI de C, y eso también facilita la interoperabilidad con otros lenguajes. Puede que yo me haya perdido algo en la evolución del tema
Y además, ¿no hay quirks en la compatibilidad ABI entre Clang y MSVC? Recuerdo que Conan explícitamente desaconsejaba o prohibía mezclar compiladores, así que me pregunto por qué el comité de C++ se esfuerza tanto por mantener la estabilidad del ABI
Aquí hay dos cosas estrechamente relacionadas: la especificación de la biblioteca estándar y la implementación. La especificación corresponde a una combinación completa de lenguaje+biblioteca, y la implementación por lo general intenta soportar al menos una o más versiones de esa especificación
Hay muchas bibliotecas que exponen interfaces de C++, incluidas algunas muy grandes, como Qt
El problema es que la máquina abstracta de C++ no define el proceso de enlace. Por eso no puede definir cómo funcionan las bibliotecas dinámicas. El enlace dinámico de C++ en sistemas UNIX sigue el modelo de C: finge ser enlace dinámico y luego le pasa el problema al loader. De ahí salen horrores como copy relocation. Windows tiene una noción mucho más principista de lo que es una biblioteca compartida, pero por eso algunos idioms de bibliotecas C++ en UNIX no pueden funcionar en Windows
Las bibliotecas compartidas son especialmente problemáticas con funciones como las plantillas de C++. Para poder instantiate una plantilla con tipos de usuario, la definición completa tiene que estar en el header, porque el compilador no puede ver más allá de los límites de la unidad de compilación. En una biblioteca compartida, el mismo código se instantiate en varios lugares. Si el programa y la biblioteca instantiate la misma plantilla con los mismos parámetros, ambos terminan con una copia, y entonces el linker y el loader tienen que asegurarse de que en el programa final cargado solo se use una
Si lo comparas con Swift, Swift sí dice explícitamente: “las bibliotecas compartidas existen y el lenguaje expone estructuras a nivel de lenguaje para representarlas”. Si quieres exponer genéricos a través del límite de una biblioteca compartida, puedes hacerlo, pero para todos los llamadores externos eso se reduce a una versión con despacho dinámico. En C++ también se puede implementar a mano. Puedes hacer una versión general de la plantilla con un wrapper de type erasure y usar explícitamente otras instantiations concretas. Pero eso es difícil y manual. En Swift simplemente es “así funciona esto en el límite de una biblioteca compartida”
Lo mismo con ocultar tipos. C++ usa el patrón
pImplpara crear unapublic interfaceque expone comportamiento más allá del límite de la biblioteca, pero oculta la implementación. Swift tiene una máquina abstracta que sabe dónde están los límites de la biblioteca y dice: “el tamaño de un tipo que no esté marcado como ABI-stable no es una constante en tiempo de compilación al otro lado del límite de una biblioteca compartida”Esa es otra forma en la que el estándar niega la realidad. Casi todas las codebases de C++ no triviales en las que he trabajado se compilaban con
-fno-rtti -fno-exceptionso las opciones equivalentes de CL.EXE. El estándar no reconoce esto como posibilidad. La mayoría de las funciones de la biblioteca estándar siguen esperando excepciones para reportar errores, así que si compilas con-fno-exception, simplemente llaman aabort. Eso vuelve inutilizables en sistemas embebidos los elementos de la biblioteca estándar que hacen asignación dinámica de memoria.std::vector<T>::push_backpuede hacer que el programa se caigaLa parte del artículo que dice “el comité no solo no puede quitar funciones malas, sino que sigue agregando funciones nuevas que los ingenieros en la práctica no pidieron” es 100% igual a cómo aparecieron los contracts. Verus muestra lo que un buen sistema de contracts puede hacer posible en un lenguaje orientado al mismo tipo de entornos que C++. Los contracts de P2900 son una combinación de requisitos en conflicto, así que empeoran todos los problemas donde los contracts podrían haber servido
No creo que sea cierta la conclusión de que un “ingeniero de C++” gana mucho más que un “ingeniero que sabe programar”. En la práctica, nadie escribe código siguiendo el estándar de C++ tal cual, sino ajustándose al subset-of-a-superset interno que más le gusta a cada quien
go vettambién tiene valor. Porque ofrece actualizaciones automáticas para mejorar APIsDesde el año pasado casi abandoné C++, primero me pasé a Kotlin y luego a Swift. En el trabajo todavía tengo que mantener C++, pero el código nuevo que escribimos es mucho más limpio, conciso y seguro. Estamos haciendo un tradeoff en tamaño de código y quizá también en rendimiento, pero vale la pena
Recordé que el significado del for loop de Go cambió rompiendo compatibilidad hacia atrás, así que pensé que esta frase estaba mal: https://go.dev/blog/loopvar-preview
Pero luego vi que Go aquí usa un enfoque parecido al de las editions de Rust. El significado solo cambia si declaras una versión de Go 1.22 o superior. Supongo que
io/ioutiltambién podría eliminarse de este modo, pero probablemente no valga lo suficiente como para romper código incluso al cruzar un límite de editionSi C++ no hubiera intentado realmente estas malas ideas y demostrado que eran malas, quizá Rust no existiría en su forma actual. ¡Muchas gracias!
Me interesa un reemplazo de la biblioteca estándar al estilo Rust para C++. Conozco rpp, que apunta a eso: https://github.com/TheNumbat/rpp
¿Hay otras opciones? No me refiero a otra implementación de la stdlib de C++ como EASTL, sino a bibliotecas que sigan más de cerca a Rust. Sé que algunas cosas como
std::initializer_listestán incrustadas en la sintaxis, pero todo lo demás se puede cambiar