
La sintaxis de C++ es el conjunto de reglas que definen cómo se escribe y compila un programa en C++ .
La sintaxis de C++ se hereda en gran medida de la sintaxis de su lenguaje antecesor, C , y ha influido en la sintaxis de varios lenguajes posteriores, entre los que se incluyen, pero no se limitan a, Java , C# y Rust .
Lo esencial
Gran parte de la sintaxis de C++ se alinea con la sintaxis de C , ya que C++ proporciona compatibilidad con versiones anteriores de C.
El programa "Hola, mundo" en C++ es el siguiente: [ 1 ]
importar std ;int main ( int argc , char * argv []) { std :: println ( "Hola, mundo!" ); }Antes de C++23 , el programa "¡Hola, mundo!" utilizaba iostreams .
#include <iostream>int main ( int argc , char * argv []) { std :: cout << "¡Hola, mundo!" << std :: endl ; }Identificador
Un identificador es el nombre de un elemento en el código . Existen ciertas convenciones de nomenclatura estándar que deben seguirse al seleccionar nombres para los elementos. En C++, los identificadores distinguen entre mayúsculas y minúsculas .
Un identificador puede contener:
- Cualquier carácter Unicode que sea una letra (incluidas las letras numéricas como los números romanos ) o un dígito.
- Símbolo monetario (como ¥).
- Carácter de puntuación de conexión (como _ ).
Un identificador no puede:
- Empieza con un dígito.
- Debe ser igual a una palabra clave reservada, un literal nulo o un literal booleano .
El identificador nullptrno es una palabra reservada, sino una constante global que hace referencia a un literal de puntero nulo . De manera similar, las palabras truey falsehacen referencia a los valores booleanos verdadero y falso, respectivamente.
Palabras clave
Palabras clave
Las siguientes palabras no pueden usarse como nombres de identificadores ni redefinirse, de las cuales hay 80. [ 2 ]
alignasalignofasmautoboolbreakcasecatchcharchar8_tchar16_tchar32_tclassconceptconstconstevalconstexprconstinitconst_castcontinuecontract_assertco_awaitco_returnco_yielddecltypedefaultdodoubledynamic_castelseenumexplicitexportexternfloatforfriendgotoifimportinlineintlongmodulemutablenamespacenewnoexceptoperatorprivateprotectedpublicregisterreinterpret_castrequiresreturnshortsignedsizeofstaticstatic_assertstatic_caststructswitchtemplatethisthread_localthrowtrytypedeftypeidtypenameunionunsignedusingvirtualvoidvolatilewchar_twhile
La palabra clave restrict, aunque presente en C, no es estándar en C++, aunque algunos compiladores pueden admitirla. La palabra clave fortran, una palabra clave admitida condicionalmente en C que denota enlace para el lenguaje de programación Fortran , es admitida condicionalmente en C++.
Palabras reservadas para valores literales
Las siguientes palabras se refieren a valores literales utilizados por el idioma, de los cuales hay 3.
truefalsenullptr
Palabras clave de operador alternativas
Las siguientes palabras son palabras clave reservadas, pero se utilizan como grafías alternativas para operadores y tokens que utilizan caracteres que no son ISO646, de los cuales hay 11.
andand_eqbitandbitorcomplnotnot_eqoror_eqxorxor_eq
Identificadores con significado especial
Las siguientes palabras pueden usarse como nombres de identificación, pero tienen significados especiales en ciertos contextos, de los cuales hay 4.
finaloverrideprepost
Directivas del preprocesador
El preprocesador reconoce los siguientes tokens en el contexto de las directivas del preprocesador, de los cuales hay 20.
#if#elif#else#endif#ifdef#ifndef#elifdef#elifndef#define#undef#include#embed#line#error#warning#pragma#__has_include#__has_cpp_attribute#__has_embeddefined(sigue una directiva condicional)
Las siguientes macros están definidas en la biblioteca estándar de C/C++:
NULL(se expande a(void*)0, utilizado antes de la introducción denullptrpara representar un puntero nulo)NDEBUG(una macro que significa "sin depuración", utilizada principalmente para deshabilitarassert()si está definida)assert()(una macro con argumentos, que finaliza el programa si el argumento se evalúa comofalse,0, onullptr, y puede deshabilitarse medianteNDEBUG)__COUNTER__(un tiempo de macro tal que cuando se usa, el preprocesador lo expande en un literal entero secuencial único, comenzando desde 0 y aumentando en 1 cada vez que se expande) [ a ]
Palabras clave de la especificación técnica
Las siguientes palabras clave aparecen en algunas especificaciones técnicas de C++, pero no en el lenguaje principal en sí.
La especificación técnica anterior sobre reflexión proponía la palabra clave reflexpr, sin embargo, la reflexión finalizada de C++26^^ utilizó un nuevo operador en lugar de reflexpr.
La especificación técnica de la memoria transaccional introduce estas palabras clave, de las cuales hay 4.
atomic_cancelatomic_commitatomic_noexceptsynchronized
Se introducen los siguientes identificadores con significado especial, de los cuales hay 2.
transaction_safetransaction_safe_dynamic
Espacios de nombres reservados
El estándar C++ reserva los espacios de nombres std(para símbolos de la biblioteca estándar de C++) y posix(sin usar, pero presumiblemente para símbolos relacionados con POSIX ). Es un comportamiento indefinido agregar declaraciones o definiciones a estos espacios de nombres, con la excepción de agregar especializaciones de plantillas a símbolos en el espacio de nombres std. El estándar C++ también reserva los nombres de módulos que coinciden con stdy std.*.
bloques de código
Los separadores { y } indican un bloque de código y un nuevo ámbito. Los miembros de una clase y el cuerpo de un método son ejemplos de lo que puede estar dentro de estas llaves en diversos contextos.
Dentro del cuerpo de los métodos, se pueden usar llaves para crear nuevos ámbitos, como sigue:
void hacerAlgo () { int a ;{ int b ; a = 1 ; }a = 2 ; b = 3 ; // Ilegal porque la variable b está declarada en un ámbito interno. }Comentarios
C++ tiene dos tipos de comentarios : comentarios tradicionales y comentarios de fin de línea .
Los comentarios tradicionales, también conocidos como comentarios en bloque, comienzan con /*y terminan con */, y pueden abarcar varias líneas.
/* Este es un comentario de varias líneas. Puede ocupar más de una línea. */Los comentarios de fin de línea comienzan con //y se extienden hasta el final de la línea actual.
// Este es un comentario de fin de líneaLos comentarios de documentación en los archivos fuente son procesados por la herramienta externa Doxygen para generar la documentación. Este tipo de comentario es idéntico a los comentarios tradicionales, excepto que comienza con /**las convenciones definidas por la herramienta Doxygen y las sigue. Técnicamente, estos comentarios son un tipo especial de comentario tradicional y no están definidos específicamente en la especificación del lenguaje.
/** * Este es un comentario de documentación. * * @author John Doe */Argumentos de la línea de comandos
Al igual que en C, los parámetros dados en una línea de comandos se pasan a un programa C++ con dos variables predefinidas: el número de argumentos de la línea de comandos argcy los argumentos individuales como cadenas de caracteres en el array de punteros argv. Por lo tanto, el comando:
miFiltro p1 p2 p3
da como resultado algo como:
Si bien las cadenas individuales son conjuntos de caracteres contiguos, no hay garantía de que se almacenen como un grupo contiguo.
El nombre del programa, argv[0], puede ser útil al imprimir mensajes de diagnóstico o para que un binario cumpla múltiples funciones. Se puede acceder a los valores individuales de los parámetros con argv[1], argv[2], y argv[3], como se muestra en el siguiente programa:
importar std ;int main ( int argc , char * argv []) { std :: println ( "argc = {}" , argc ); for ( size_t i = 0 ; i < argc ; ++ i ) { std :: println ( "argv[{}] = {}" , i , argv [ i ]); } }objetos
C++ introduce características de programación orientada a objetos (POO) en C. Ofrece clases que proporcionan las cuatro características comunes en lenguajes POO (y algunos lenguajes que no son POO): abstracción , encapsulación , herencia y polimorfismo . Una característica distintiva de las clases de C++ en comparación con las clases de otros lenguajes de programación es la compatibilidad con destructores deterministas , que a su vez brindan soporte para el concepto de Adquisición de Recursos mediante Inicialización (RAII).
Almacenamiento de objetos
Al igual que en C, C++ admite cuatro tipos de gestión de memoria : objetos de duración de almacenamiento estático, objetos de duración de almacenamiento de subprocesos, objetos de duración de almacenamiento automático y objetos de duración de almacenamiento dinámico. [ 4 ]
Objetos de duración de almacenamiento estático
Los objetos de duración de almacenamiento estático se crean antes de main()que se ingrese al programa (véanse las excepciones a continuación) y se destruyen en orden inverso al de su creación después de main()que este finalice. El estándar no especifica el orden exacto de creación (aunque existen algunas reglas definidas a continuación) para permitir a las implementaciones cierta libertad en la organización de su implementación. Formalmente, los objetos de este tipo tienen una vida útil que "durará durante la ejecución del programa". [ 5 ]
Los objetos de duración de almacenamiento estático se inicializan en dos fases. Primero, se realiza la "inicialización estática" y, solo después de que se haya realizado toda la inicialización estática, se realiza la "inicialización dinámica". En la inicialización estática, todos los objetos se inicializan primero con ceros; después, todos los objetos que tienen una fase de inicialización constante se inicializan con la expresión constante (es decir, las variables inicializadas con un literal o constexpr ). Aunque no está especificado en el estándar, la fase de inicialización estática puede completarse en tiempo de compilación y guardarse en la partición de datos del ejecutable. La inicialización dinámica implica toda la inicialización de objetos realizada a través de un constructor o una llamada a función (a menos que la función esté marcada con , en C++11). El orden de inicialización dinámica se define como el orden de declaración dentro de la unidad de compilación (es decir, el mismo archivo). No se proporciona ninguna garantía sobre el orden de inicialización entre unidades de compilación. constexpr
objetos de duración de almacenamiento de subprocesos
Las variables de este tipo son muy similares a los objetos de duración de almacenamiento estático. La principal diferencia es que el momento de creación es justo antes de la creación del hilo, y la destrucción se realiza después de que el hilo se haya unido. [ 6 ]
objetos de duración de almacenamiento automático
Los tipos de variables más comunes en C++ son las variables locales dentro de una función o bloque, y las variables temporales. [ 7 ] La característica común de las variables automáticas es que tienen una vida útil limitada al ámbito de la variable. Se crean y potencialmente se inicializan en el punto de declaración (ver más abajo para más detalles) y se destruyen en el orden inverso al de su creación cuando se abandona el ámbito. Esto se implementa mediante la asignación en la pila .
Las variables locales se crean cuando el punto de ejecución pasa el punto de declaración. Si la variable tiene un constructor o inicializador, este se utiliza para definir el estado inicial del objeto. Las variables locales se destruyen cuando se cierra el bloque o la función local en la que se declaran. Los destructores de variables locales de C++ se llaman al final del ciclo de vida del objeto, lo que permite una gestión automática de recursos denominada RAII , ampliamente utilizada en C++.
Las variables miembro se crean al crear el objeto padre. Los miembros de un array se inicializan desde el 0 hasta el último, en orden. Las variables miembro se destruyen al destruirse el objeto padre, en orden inverso al de su creación. Es decir, si el padre es un objeto automático, se destruirá cuando salga del ámbito, lo que desencadena la destrucción de todos sus miembros.
Las variables temporales se crean como resultado de la evaluación de una expresión y se destruyen cuando la instrucción que contiene la expresión se ha evaluado por completo (normalmente al ;final de una instrucción).
objetos de duración de almacenamiento dinámico
Estos objetos tienen una vida útil dinámica y pueden crearse directamente con una llamada a newy destruirse explícitamente con una llamada a delete. [ 8 ] C++ también admite mallocy free, de C, pero estos no son compatibles con newy delete. El uso de newdevuelve una dirección a la memoria asignada. Las Directrices principales de C++ aconsejan no usar newdirectamente para crear objetos dinámicos a favor de punteros inteligentes a través de para propiedad única y para propiedad múltiple con conteo de referencias, [ 9 ] que se introdujeron en C++11. make_unique<T>make_shared<T>
Interoperabilidad
Con C
A menudo se considera que C++ es un superconjunto de C , pero esto no es del todo cierto. [ 10 ] La mayoría del código C se puede compilar fácilmente de forma correcta en C++, pero existen algunas diferencias que hacen que cierto código C válido sea inválido o se comporte de manera diferente en C++. Por ejemplo, C permite la conversión implícita de a otros tipos de puntero, pero C++ no (por razones de seguridad de tipos). Además, C++ define muchas palabras clave nuevas, como y , que pueden usarse como identificadores (por ejemplo, nombres de variables) en un programa C. void*newclass
Algunas incompatibilidades se eliminaron con la revisión de 1999 del estándar C ( C99 ), que ahora admite características de C++ como comentarios de línea ( //) y declaraciones mezcladas con código. Por otro lado, C99 introdujo una serie de características nuevas que C++ no admitía y que eran incompatibles o redundantes en C++, como arreglos de longitud variable , tipos de números complejos nativos (sin embargo, la std::complex<T>clase en la biblioteca estándar de C++ proporciona una funcionalidad similar, aunque no es compatible con el código), inicializadores designados, literales compuestos y la restrictpalabra clave. [ 11 ] Algunas de las características introducidas por C99 se incluyeron en la versión posterior del estándar C++, C++11 (de entre las que no eran redundantes). [ 12 ] [ 13 ] [ 14 ] Sin embargo, el estándar C++11 introduce nuevas incompatibilidades, como no permitir la asignación de un literal de cadena a un puntero de carácter, que sigue siendo válido en C.
Para combinar código C y C++, cualquier declaración o definición de función que se vaya a llamar o usar tanto en C como en C++ debe declararse con enlace C, colocándola dentro de un bloque. Dicha función no puede depender de características que se basen en la modificación de nombres (es decir, la sobrecarga de funciones). extern"C"{/* ... */}
Ensamblaje en línea
Los programas desarrollados en C o C++ suelen utilizar el ensamblador en línea para aprovechar sus funcionalidades de bajo nivel, mayor velocidad y control mejorado en comparación con los lenguajes de programación de alto nivel [ 15 ] [ 16 ] cuando la optimización del rendimiento es esencial. C++ ofrece soporte para la incrustación de lenguaje ensamblador mediante declaraciones asm [ 17 ] , pero la compatibilidad del ensamblador en línea varía significativamente entre compiladores y arquitecturas. A diferencia de las características de los lenguajes de alto nivel como Python o Java , el código ensamblador depende en gran medida del procesador subyacente y de la implementación del compilador.
Variaciones entre compiladores
Los distintos compiladores de C++ implementan el ensamblador en línea de maneras diferentes.
- GCC ( GNU Compiler Collection ) y Clang : [ 18 ] Utilice la sintaxis de ensamblador en línea extendida de GCC. Utilice
__asm__la palabra clave en lugar deasmcuando escriba código que pueda compilarse con las opciones y , lo que permite especificar operandos de entrada/salida y registros modificados. Este enfoque es ampliamente adoptado, incluso por los compiladores de Intel [ 19 ] e IBM [ 20 ] .-ansi-std - MSVC ( Microsoft Visual C++ ): El ensamblador en línea está integrado en el compilador. Anteriormente, se admitía el ensamblador en línea mediante la
__asmpalabra clave, pero esta compatibilidad se eliminó en el modo de 64 bits, requiriendo en su lugar módulos .asm separados. [ 21 ] - TI ARM Clang y compiladores embebidos: [ 22 ] Algunos compiladores de sistemas embebidos, como TI Arm Clang de Texas Instruments, permiten el ensamblaje en línea, pero imponen reglas más estrictas para evitar conflictos con las convenciones de registro y las convenciones de llamada.
Interoperabilidad entre C++ y Assembly
C++ proporciona dos métodos principales para integrar código ASM.
- Archivos de ensamblaje independientes: el código de ensamblaje se escribe por separado y se enlaza con el código C++. [ 23 ]
- Ensamblaje en línea : el código ensamblador se incrusta dentro del código C++ mediante extensiones específicas del compilador.
Por ejemplo, al llamar a una función de ensamblador desde C++, utilice esto para evitar la modificación de nombres de C++. extern"C"
// main.cpp import std ;extern "C" int addFromAsm ( int , int ); // Declarar la función de ensamblajeint main () { int result = addFromAsm ( 5 , 7 ); std :: println ( "Resultado del ensamblaje: {}" , result ); return 0 ; }# Código ensamblador usando la arquitectura RISC-V .section .text .global addFromAsmaddFromAsm: agregar a0 , a0 , a1 # Agregar el primer argumento (a0) y el segundo argumento (a1), almacenar en a0 ret # Devolver (a0 contiene el valor de retorno)Las variables globales en lenguaje ensamblador deben declararse como externen C++ y marcarse .globalen lenguaje ensamblador.
// main.cpp import std ;extern "C" int globalVar ; // Declarar variable global desde el ensambladoint main () { std :: println ( "Variable global del ensamblado: {}" , globalVar ); return 0 ; }# asm usando la arquitectura RISC-V .section .data .global globalVar .align 4 globalVar: .word 42 # Definir valor enteroEl ensamblador en línea permite incrustar ASM directamente en C++ usando la asmpalabra clave.
// main.cpp (compilado en GCC/Clang) import std ;int main () { int x = 10 ; int y = 20 ; int sum ;asm volatile ( "add %0, %1, %2" : "=r" ( sum ) // Operando de salida (almacenado en un registro) : "r" ( x ), "r" ( y ) // Operandos de entrada (almacenados en registros) );std :: println ( "Suma usando ensamblador en línea: {}" , suma ); return 0 ; }Encapsulación
La encapsulación consiste en ocultar información para garantizar que las estructuras de datos y los operadores se utilicen según lo previsto y para que el modelo de uso sea más claro para el desarrollador. C++ ofrece la posibilidad de definir clases y funciones como sus principales mecanismos de encapsulación. Dentro de una clase, los miembros pueden declararse como públicos, protegidos o privados para garantizar explícitamente la encapsulación. Un miembro público de la clase es accesible para cualquier función. Un miembro privado solo es accesible para las funciones que pertenecen a esa clase y para las funciones y clases a las que la clase les ha otorgado explícitamente permiso de acceso ("amigos"). Un miembro protegido es accesible para los miembros de las clases que heredan de la clase, además de la propia clase y sus amigos.
El principio de programación orientada a objetos garantiza la encapsulación de todas y solo las funciones que acceden a la representación interna de un tipo. C++ admite este principio mediante funciones miembro y funciones amigas, pero no lo impone. Los programadores pueden declarar como públicas partes o la totalidad de la representación de un tipo, y pueden hacer públicas entidades que no formen parte de dicha representación. Por lo tanto, C++ admite no solo la programación orientada a objetos, sino también otros paradigmas de descomposición, como la programación modular .
Generalmente se considera una buena práctica mantener todos los datos privados o protegidos, y hacer públicas solo aquellas funciones que forman parte de una interfaz mínima para los usuarios de la clase. Esto puede ocultar los detalles de la implementación de los datos, permitiendo al diseñador modificar fundamentalmente la implementación posteriormente sin alterar la interfaz. [ 24 ] [ 25 ]
Herencia
La herencia permite que un tipo de dato adquiera propiedades de otros tipos de datos. La herencia de una clase base puede declararse como pública, protegida o privada. Este especificador de acceso determina si las clases no relacionadas y derivadas pueden acceder a los miembros públicos y protegidos heredados de la clase base. Solo la herencia pública corresponde a lo que generalmente se entiende por "herencia". Las otras dos formas se utilizan con mucha menos frecuencia. Si se omite el especificador de acceso, una "clase" hereda de forma privada, mientras que una "estructura" hereda de forma pública. Las clases base pueden declararse como virtuales; esto se denomina herencia virtual . La herencia virtual garantiza que solo exista una instancia de una clase base en el grafo de herencia, evitando algunos de los problemas de ambigüedad de la herencia múltiple.
La herencia múltiple es una característica de C++ que permite que una clase se derive de más de una clase base; esto permite relaciones de herencia más elaboradas. Por ejemplo, una clase "Flying Cat" puede heredar tanto de "Cat" como de "Flying Mammal". Algunos otros lenguajes, como C# o Java , logran algo similar (aunque más limitado) al permitir la herencia de múltiples interfaces mientras restringen el número de clases base a una (las interfaces, a diferencia de las clases, solo proporcionan declaraciones de funciones miembro, no implementación ni datos miembro). Una interfaz como en C# y Java se puede definir en C++ como una clase que contiene solo funciones virtuales puras, a menudo conocida como clase base abstracta o "ABC". Las funciones miembro de dicha clase base abstracta normalmente se definen explícitamente en la clase derivada, no se heredan implícitamente. La herencia virtual de C++ presenta una característica de resolución de ambigüedad llamada dominancia .
Operadores y sobrecarga de operadores
C++ ofrece más de 35 operadores, que abarcan aritmética básica, manipulación de bits, indirección, comparaciones, operaciones lógicas y otros. Casi todos los operadores se pueden sobrecargar para tipos definidos por el usuario, con algunas excepciones notables como el acceso a miembros ( .y .*) y el operador condicional. El amplio conjunto de operadores sobrecargables es fundamental para que los tipos definidos por el usuario en C++ se comporten como tipos integrados.
Los operadores sobrecargables también son una parte esencial de muchas técnicas avanzadas de programación en C++, como los punteros inteligentes . Sobrecargar un operador no cambia la precedencia de los cálculos que lo involucran, ni cambia la cantidad de operandos que utiliza (sin embargo, el operador puede ignorar cualquier operando, aunque se evaluará antes de la ejecución). Los operadores sobrecargados " &&" y " ||" pierden su propiedad de evaluación de cortocircuito .
Polimorfismo
El polimorfismo permite una interfaz común para muchas implementaciones, y que los objetos actúen de manera diferente en distintas circunstancias.
C++ admite varios tipos de polimorfismo estático (que se resuelve en tiempo de compilación ) y dinámico (que se resuelve en tiempo de ejecución ) , gracias a las características del lenguaje descritas anteriormente. El polimorfismo en tiempo de compilación no permite ciertas decisiones en tiempo de ejecución, mientras que el polimorfismo en tiempo de ejecución suele conllevar una penalización en el rendimiento.
Polimorfismo dinámico
Herencia
En C++, los punteros variables y las referencias a un tipo de clase base también pueden hacer referencia a objetos de cualquier clase derivada de ese tipo. Esto permite que los arreglos y otros tipos de contenedores almacenen punteros a objetos de diferentes tipos (las referencias no se pueden almacenar directamente en los contenedores). Esto posibilita el polimorfismo dinámico (en tiempo de ejecución), donde los objetos referenciados pueden comportarse de manera diferente según sus tipos (reales o derivados).
C++ también proporciona el dynamic_castoperador, que permite que el código intente de forma segura la conversión de un objeto, a través de una referencia/puntero base, a un tipo más derivado: downcasting . El intento es necesario ya que a menudo no se sabe a qué tipo derivado se hace referencia. ( Upcasting , conversión a un tipo más general, siempre se puede comprobar/realizar en tiempo de compilación a través de static_cast, ya que las clases ancestrales se especifican en la interfaz de la clase derivada, visible para todos los llamadores). dynamic_castse basa en la información de tipo en tiempo de ejecución (RTTI), metadatos en el programa que permiten diferenciar los tipos y sus relaciones. Si dynamic_castfalla una conversión a un puntero, el resultado es la nullptrconstante, mientras que si el destino es una referencia (que no puede ser nula), la conversión lanza una excepción. Los objetos que se sabe que son de un cierto tipo derivado se pueden convertir a ese con static_cast, omitiendo RTTI y la comprobación de tipo segura en tiempo de ejecución de dynamic_cast, por lo que esto solo debe usarse si el programador está muy seguro de que la conversión es, y siempre será, válida.
Funciones de miembro virtual
Normalmente, cuando una función de una clase derivada sobrescribe una función de una clase base, la función a llamar se determina por el tipo del objeto. Una función dada se sobrescribe cuando no existe diferencia en el número o tipo de parámetros entre dos o más definiciones de esa función. Por lo tanto, en tiempo de compilación, puede que no sea posible determinar el tipo del objeto y, por consiguiente, la función correcta a llamar, dado solo un puntero a la clase base; por lo tanto, la decisión se pospone hasta el tiempo de ejecución. Esto se denomina despacho dinámico . Las funciones o métodos miembros virtuales [ 26 ] permiten que se llame a la implementación más específica de la función, de acuerdo con el tipo real del objeto en tiempo de ejecución. En las implementaciones de C++, esto se suele hacer mediante tablas de funciones virtuales . Si se conoce el tipo del objeto, esto se puede evitar anteponiendo un nombre de clase completo antes de la llamada a la función, pero en general las llamadas a funciones virtuales se resuelven en tiempo de ejecución.
Además de las funciones miembro estándar, las sobrecargas de operadores y los destructores pueden ser virtuales. Una regla general, basada en la experiencia práctica, establece que si alguna función de la clase es virtual, su destructor también debería serlo. Dado que el tipo de un objeto se conoce en tiempo de compilación al crearlo, los constructores, y por extensión los constructores de copia, no pueden ser virtuales. Sin embargo, puede darse el caso de que sea necesario crear una copia de un objeto cuando se pasa un puntero a un objeto derivado como puntero a un objeto base. En tal caso, una solución común es crear una función virtual (o similar) que cree y devuelva una copia de la clase derivada al ser llamada. clone()
Una función miembro también puede hacerse "virtual pura" añadiéndole después del paréntesis de cierre y antes del punto y coma. Una clase que contiene una función virtual pura se denomina clase abstracta . No se pueden crear objetos a partir de una clase abstracta; solo se pueden derivar de ella. Cualquier clase derivada hereda la función virtual como pura y debe proporcionar una definición no pura de la misma (y de todas las demás funciones virtuales puras) antes de que se puedan crear objetos de la clase derivada. Un programa que intenta crear un objeto de una clase con una función miembro virtual pura o una función miembro virtual pura heredada es un programa mal formado. =0
Polimorfismo estático
La sobrecarga de funciones permite que los programas declaren varias funciones con el mismo nombre pero con diferentes argumentos ( polimorfismo ad hoc ). Las funciones se distinguen por el número o el tipo de sus parámetros formales . Por lo tanto, un mismo nombre de función puede referirse a diferentes funciones según el contexto en el que se utilice. El tipo de retorno de la función no se utiliza para distinguir las funciones sobrecargadas, y los diferentes tipos de retorno generarían un error de compilación.
Al declarar una función, un programador puede especificar un valor predeterminado para uno o más parámetros . Esto permite omitir opcionalmente los parámetros con valores predeterminados al llamar a la función, en cuyo caso se utilizarán los argumentos predeterminados. Cuando se llama a una función con menos argumentos que parámetros declarados, los argumentos explícitos se asocian a los parámetros de izquierda a derecha, y a los parámetros sin asociación al final de la lista se les asignan sus argumentos predeterminados. En muchos casos, especificar argumentos predeterminados en una sola declaración de función es preferible a proporcionar definiciones de funciones sobrecargadas con diferente número de parámetros.
Plantillas
Las plantillas de C++ permiten la programación genérica . C++ admite plantillas de funciones, clases, alias y variables. Las plantillas pueden parametrizarse mediante tipos, constantes de tiempo de compilación y otras plantillas. Las plantillas se implementan mediante instanciación en tiempo de compilación. Para instanciar una plantilla, los compiladores sustituyen argumentos específicos por los parámetros de la plantilla para generar una instancia concreta de función o clase. Algunas sustituciones no son posibles; estas se eliminan mediante una política de resolución de sobrecarga descrita por la frase "Un fallo de sustitución no es un error " (SFINAE). Las plantillas son una herramienta poderosa que se puede utilizar para la programación genérica , la metaprogramación con plantillas y la optimización de código, pero esta potencia implica un coste. El uso de plantillas puede aumentar el tamaño del código objeto , ya que cada instanciación de plantilla produce una copia del código de la plantilla: una por cada conjunto de argumentos de la plantilla; sin embargo, esta cantidad de código es la misma o menor que la que se generaría si el código se escribiera manualmente. [ 27 ] Esto contrasta con los genéricos en tiempo de ejecución que se ven en otros lenguajes (por ejemplo, Java ) donde en tiempo de compilación se borra el tipo y se conserva un único cuerpo de plantilla.
usando std :: string ;plantilla < typename T > clase Box { privado : T valor ; público : explícito Box ( T v ) : valor { v } {}void show () { std :: println ( "Valor: {}" , value ); } };Caja < int > caja1 ( 100 ); Caja < string > caja2 ( "¡Hola, mundo!" );caja1.mostrar ( ) ; caja2.mostrar ( ) ;Las plantillas son diferentes de las macros : si bien ambas características del lenguaje en tiempo de compilación permiten la compilación condicional, las plantillas no se limitan a la sustitución léxica. Las plantillas conocen la semántica y el sistema de tipos de su lenguaje asociado, así como todas las definiciones de tipos en tiempo de compilación, y pueden realizar operaciones de alto nivel, incluido el control de flujo programático basado en la evaluación de parámetros con comprobación de tipos estricta. Las macros son capaces de controlar la compilación de forma condicional según criterios predeterminados, pero no pueden instanciar nuevos tipos, realizar recursión ni evaluación de tipos y, en efecto, se limitan a la sustitución de texto y la inclusión/exclusión de texto antes de la compilación. En otras palabras, las macros pueden controlar el flujo de compilación basándose en símbolos predefinidos, pero no pueden, a diferencia de las plantillas, instanciar nuevos símbolos de forma independiente. Las plantillas son una herramienta para el polimorfismo estático (véase más adelante) y la programación genérica .
Además, las plantillas son un mecanismo de compilación en C++ que es Turing-completo , lo que significa que cualquier cálculo expresable por un programa informático puede ser calculado, de alguna forma, por un metaprograma de plantilla antes del tiempo de ejecución.
En resumen, una plantilla es una función o clase parametrizada en tiempo de compilación, escrita sin conocer los argumentos específicos utilizados para su instanciación. Tras la instanciación, el código resultante es equivalente al código escrito específicamente para los argumentos pasados. De esta forma, las plantillas permiten desacoplar los aspectos genéricos y de amplia aplicación de funciones y clases (codificados en las plantillas) de los aspectos específicos (codificados en los parámetros de la plantilla) sin sacrificar el rendimiento debido a la abstracción.
Las plantillas en C++ proporcionan un mecanismo sofisticado para escribir código genérico y polimórfico (es decir, polimorfismo paramétrico ). En particular, mediante el patrón de plantilla recurrente , es posible implementar una forma de polimorfismo estático que imita fielmente la sintaxis para sobrescribir funciones virtuales. Dado que las plantillas de C++ reconocen tipos y son Turing-completas , también se pueden usar para que el compilador resuelva condicionales recursivos y genere programas sustanciales mediante metaprogramación con plantillas . Contrariamente a algunas opiniones, el código de plantilla no genera código masivo después de la compilación con la configuración adecuada del compilador. [ 27 ]
C++ también incluye plantillas variádicas . Estas pueden utilizarse para implementar funciones variádicas con seguridad de tipos , ya que, de lo contrario, C++ solo dispone de las funciones variádicas de C, que no tienen seguridad de tipos.
usando std :: common_type_t ;plantilla < typename ... Args > common_type_t < Args ... > suma ( Args ... args ) { return ( args + ...); }Afirmaciones y contratos
Afirmaciones
En C++, existen dos formas de aserciones sin contrato : aserciones en tiempo de ejecución y aserciones en tiempo de compilación .
Las aserciones en tiempo de ejecución se realizan utilizando la assert()macro, que es proporcionada por el <cassert>encabezado (heredado a su vez del encabezado C <assert.h>). Si la expresión dentro de la assert()macro es falsa, imprimirá un mensaje de error en el flujo de error estándar y detendrá el programa, proporcionando el nombre del archivo fuente y la línea fuente (obtenidos de __FILE__y __LINE__).
usando std :: vector ;int getAtIndex ( const vector <int> & v , size_t i ) { assert ( i >= 0 ); // Asegura que i sea al menos 0 assert ( i < v.size ( ) ); // Asegura que i sea menor que la longitud de v return v [ i ] ; }La presencia de la bandera NDEBUGdesactiva las aserciones.
Las aserciones en tiempo de compilación se realizan mediante una static_assertinstrucción (introducida en C++11). static_assertes una palabra clave, a diferencia assertde que es una macro. Las aserciones estáticas, que se aplican en la compilación, se utilizan cuando se verifican condiciones durante la compilación. Históricamente, esto las ha hecho útiles en la metaprogramación de plantillas , especialmente con patrones SFINAE (antes de la introducción de conceptos en C++20.
usando std :: is_integral_v ;plantilla < typename T > void process () { static_assert ( is_integral_v < T > , "T debe ser un tipo entero" ); // ... }proceso < int > (); // OK proceso < double > (); // Error de compilaciónContratos
Los contratos (desde C++26) son un sistema de aserciones de primera clase que permite la aplicación en tiempo de ejecución de precondiciones, postcondiciones y estado interno. [ 28 ] [ 29 ]
int f ( const int x ) pre ( x != 1 ) // una aserción de precondición post ( r : r == x && r != 2 ) // una aserción de postcondición; r nombra el objeto resultante de f { contract_assert ( x != 3 ); // una declaración de aserción return x ; }Aquí, C++ indica las precondiciones y postcondiciones con prey postrespectivamente, dentro de la firma de la función. [ 30 ]
En particular, C++ introduce una contract_assertinstrucción que se utiliza para verificar una condición interna; la violación de un contrato invoca al manejador de violación de contrato, std::contracts::contract_violationse produce un objeto de tipo y se pasa al manejador de violación de contrato, una función en el espacio de nombres global con la siguiente firma (que opcionalmente puede ser ). Esto también puede llamarse manualmente usando . [ 31 ]voidhandle_contract_violation(contract_violationcv);noexceptstd::contracts::invoke_default_contract_violation_handler()
Lo que hace el entorno de ejecución está determinado por std::contracts::evaluation_semantic: [ 29 ]
evaluation_semantic::ignore: la evaluación de una afirmación contractual no tiene efecto.evaluation_semantic::observe: se genera un diagnóstico y se llama al manejador de violaciones, y el programa continúa después del punto de evaluación de la afirmación del contrato.evaluation_semantic::enforceSe llama al gestor de infracciones y, a continuación, se da por terminado el programa según el contrato.evaluation_semantic::quick_enforce: el programa finaliza su contrato sin llamar al controlador de infracciones.
Expresiones Lambda
C++ proporciona soporte para funciones anónimas , también conocidas como expresiones lambda , con la forma general . [captures]<template-parameters>(params) specsrequires (...) -> returns { body }
Desde C++20, la palabra clave templatees opcional para los parámetros de plantilla de las expresiones lambda.
Si la función lambda no toma parámetros y no voidse utiliza ningún tipo de retorno (devuelve ) u otros especificadores, ()se puede omitir el para los parámetros; es decir, . [captures] { body }
El tipo de retorno de una expresión lambda se puede inferir automáticamente, si es posible; por ejemplo:
[]( int x , int y ) { return x + y ; } // inferido []( int x , int y ) [[ nodiscard ]] -> int { return x + y ; } // explícito, con atributoLa lista admite la definición de cierres . Dichas expresiones lambda se definen en el estándar como azúcar sintáctico para un objeto de función sin nombre . [captures]
Corrutinas
C++20 introduce soporte para corrutinas , que son funciones sin pila cuya ejecución puede suspenderse, devolviendo el valor a quien las llamó mientras los datos necesarios para reanudar la ejecución se almacenan por separado de la pila, lo que permite una ejecución asíncrona y no bloqueante. [ 32 ]
Se introducen las siguientes palabras clave:
co_await: realiza una espera , suspendiendo la ejecución hasta su reanudación y devolviendo el control al llamador.co_yield: suspende la ejecución de la corrutina actual y devuelve un valor a quien la llamó.co_return: completa la ejecución devolviendo un valor
El objeto promesa debe definirse exactamente con el nombre promise_typecomo un tipo anidado (para cualquier tipo Ttiene el nombre T::promise_type). Tenga en cuenta que esto no está relacionado con el std::promise<T>tipo.
Se introdujo una clase generadorastd::generator<Ref, V, Alloc> (donde Vy Allocson por defecto void), que genera una secuencia de elementos reanudando la corrutina de la que se obtuvo.
importar std ;usando std :: generator ;generador < int > rango ( int n ) noexcept { para ( int i = 0 ; i < n ; ++ i ) { co_yield i ; } }std::execution::taskSe introdujo una clase de tarea, , en C++26 . [ 33 ] Para llamarla, use std::execution::sync_wait(), que devuelve std::optional<std::tuple<Ts...>>. [ 34 ]
importar std ;using std :: optional ; using std :: tuple ; using std :: execution :: task ;tarea < int > agregar ( int a , int b ) noexcept { co_return a + b ; }tarea < int > prueba () { int ret = co_await add ( 1 , 2 ); std :: println ( "Return {}" , ret ); co_return ret ; }int main ( int argc , char * argv []) { optional < tuple < int >> result = std :: execution :: sync_wait ( test ()); std :: println ( "Resultado: {}" , std :: get < 0 > ( result ). value_or ( std :: make_tuple ( -1 )));devolver 0 ; }Cabe destacar que std::promisey std::future, aunque parecería que serían objetos awaitable, no implementan ninguno de los mecanismos necesarios para ser devueltos por corrutinas y ser esperados usando co_await. Los programadores deben implementar una serie de funciones miembro públicas, como await_ready, await_suspend, y await_resumeen el tipo de retorno para que el tipo sea esperado.
constevalfunciones, constructores, destructores, y main()no pueden ser corrutinas.
Manejo de excepciones
El manejo de excepciones se utiliza para comunicar la existencia de un problema o error en tiempo de ejecución desde donde se detectó hasta donde se puede manejar el problema. [ 35 ] Esto permite que se haga de manera uniforme y separada del código principal, mientras se detectan todos los errores. [ 36 ] Si ocurre un error, se lanza (genera) una excepción, que luego es capturada por el manejador de excepciones adecuado más cercano. La excepción hace que se salga del ámbito actual, y también de cada ámbito externo (propagación) hasta que se encuentre un manejador adecuado, llamando a su vez a los destructores de cualquier objeto en estos ámbitos salidos. [ 37 ] Al mismo tiempo, una excepción se presenta como un objeto que contiene los datos sobre el problema detectado. [ 38 ]
Algunas guías de estilo de C++, como las de Google, [ 39 ] LLVM, [ 40 ] y Qt, [ 41 ] prohíben el uso de excepciones. En algunos casos, las excepciones no se pueden usar por razones técnicas. Un ejemplo de ello es un componente crítico de un sistema embebido, donde se debe garantizar que cada operación se complete dentro de un tiempo determinado. Esto no se puede determinar con excepciones, ya que no existen herramientas para determinar el tiempo máximo necesario para manejar una excepción. [ 42 ]
El código que provoca la excepción se coloca dentro de un trybloque. Las excepciones se manejan en catchbloques separados (los manejadores); cada trybloque puede tener varios manejadores de excepciones, como se puede ver en el ejemplo siguiente. [ 43 ]
importar std ;using std :: exception ; using std :: out_of_range ; using std :: vector ;int main () { try { vector < int > vec { 3 , 4 , 3 , 1 }; int i = vec . at ( 10 ); // Lanza una excepción, std::out_of_range } catch ( const out_of_range & e ) { // Un manejador de excepciones, captura std::out_of_range, que es lanzada por vec.at(4) std :: println ( stderr , "Accediendo a un elemento inexistente: {}" , e . what ()); } catch ( const exception & e ) { // Para capturar cualquier otra excepción de la biblioteca estándar (derivan de std::exception) std :: println ( stderr , "Excepción lanzada: {}" , e . what ()); } }Se puede lanzar una excepción utilizando la throwpalabra clave; estas excepciones se manejan de la forma habitual.
usando std :: domain_error ;int addPositiveNumbers ( int a , int b ) { if ( a < 0 || b < 0 ) { throw domain_error ( "Los argumentos no eran no negativos" ); } return a + b ; }Una excepción capturada puede relanzarse mediante una instrucción, que continúa propagando el objeto lanzado hacia arriba en la pila: throw;
using std :: domain_error ; using std :: stacktrace ;void tryAdd ( int m , int n ) { try { int result = addPositiveNumbers ( m , n ); } catch ( const domain_error & e ) { std :: println ( stderr , "Se detectó un error de dominio: {}" , e.what ()); std :: println ( stderr , "{}" , stacktrace :: current ( )); throw ; // equivalente a throw e; } }Las excepciones suelen capturarse por constreferencia (es decir, ), ya que la captura por referencia evita una copia innecesaria que se produce al capturar por valor, y también evita la segmentación del tipo de excepción. Un bloque captura cualquier objeto lanzado, pero no puede hacer referencia al objeto capturado, ya que este bloque no especifica su tipo ni le asigna un identificador; simplemente captura cualquier objeto que cruce su marco de pila. catch(constinvalid_argument&e)catch(...)
Desde C++26, se pueden lanzar excepciones en tiempo de compilación (llamadas excepciones constexpr y declaradas con constructores); la más notable de las cuales es , el tipo de excepción que se lanza cuando falla una operación de función reflectiva . [ 44 ]constevalstd::meta::exception
Para contar el número de excepciones que se han lanzado (o vuelto a lanzar) en el hilo actual y que aún no han entrado en un catchbloque, std::uncaught_exceptions()se utiliza la función. [ 45 ]
También se puede especificar que una función no genere excepciones usando el noexceptespecificador; una violación de esto (es decir, una excepción que pasa el marco de pila de una noexceptfunción) llama inmediatamente a std::terminate(). También se puede especificar condicionalmente a que otra función sea noexcept, de la siguiente manera:
void wontThrow () noexcept ; void mightThrow ();// El primer noexcept es la cláusula noexcept, el segundo es el operador noexcept que se evalúa a un valor booleano void f () noexcept ( noexcept ( mightThrow ()));A diferencia de lenguajes como Java, C# y D, que solo permiten lanzar excepciones de un tipo base (por ejemplo, en Java, solo se pueden lanzar excepciones de objetos que extienden java.lang.Throwable, cuyas subclases directas son java.lang.Errory java.lang.Exception), C++ permite lanzar y capturar excepciones de cualquier objeto. No obstante, C++ utiliza una std::exceptionclase que actúa como tipo raíz de excepción, de la cual se derivan las excepciones de la biblioteca estándar y de la cual, por lo general, se derivan los tipos de excepción definidos por el usuario.
A diferencia del manejo de señales , en el que la función de manejo se llama desde el punto de falla, el manejo de excepciones sale del ámbito actual antes de que se ingrese al bloque catch, que puede estar ubicado en la función actual o en cualquiera de las llamadas a funciones anteriores que se encuentren actualmente en la pila.
Antes de C++17 (y cuando se dejó de usar por primera vez en C++11), C++ tenía especificaciones de excepciones dinámicas, que podían considerarse similares a throwslas cláusulas de Java. Por ejemplo, el siguiente código era válido en C++03:
#include <stdexcept>using std :: domain_error ; using std :: invalid_argument ;// Esto podría ser similar a la firma de Java // void performSomeOperation(int a, int b) throws InvalidArgumentException, ArithmeticException; void performSomeOperation ( int a , int b ) throw ( invalid_argument , domain_error ) { // ... }Sin embargo, estas especificaciones no se aplicaban en tiempo de compilación. Las violaciones provocaban la llamada a la función de la biblioteca estándar std::unexpected()[ b ] . [ 46 ] Si bien finalmente se eliminaron en favor del noexceptespecificador, una especificación de excepción vacía ( ) sigue siendo válida (y es equivalente a ). throw()noexcept
Tipos enumerados
C++ tiene tipos de enumeración que se heredan directamente de los de C y funcionan de forma muy similar, excepto que una enumeración es un tipo real en C++, lo que proporciona comprobaciones adicionales en tiempo de compilación. Además (al igual que con las estructuras), la enumpalabra clave de C++ se combina con un typedef , de modo que en lugar de referirse al tipo como , se puede simplemente referirse a él como . Esto se puede simular en C usando un typedef:enumMyEnumMyEnumtypedefenum{VALUE_1,VALUE_2}MyEnum;
C++11 también proporciona un segundo tipo de enumeración, denominada enumeración con ámbito . Estas son seguras en cuanto a tipos: los enumeradores no se convierten implícitamente a un tipo entero. Entre otras cosas, esto permite definir la transmisión de E/S para el tipo de enumeración. Otra característica de las enumeraciones con ámbito es que los enumeradores deben estar calificados por el nombre de la enumeración, por lo que su uso requiere anteponer el nombre de la enumeración (por ejemplo, Color::REDpara el primer enumerador en el ejemplo siguiente), a menos que se haya utilizado una declaración (introducida en C++20 ) para incorporar los enumeradores al ámbito actual. Una enumeración con ámbito se especifica mediante la frase (o ). Por ejemplo: usingenumenumclassenumstruct
// Enumeración sin ámbito de estilo C enum CardSuit { CLUBS , DIAMONDS , SPADES , HEARTS , };// Enumeración con ámbito al estilo C++11 clase Color { ROJO , VERDE , AZUL , };int main ( int argc , char * argv []) { Color c = Color :: RED ;// Enumeración estilo C sin ámbito CardSuit card1 = Clubs ; // Puede ser sin ámbito CardSuit card2 = CardSuit :: SPADES ; // Normalmente se declara con ámbito }El tipo subyacente de una enumeración es un tipo entero definido por la implementación que es lo suficientemente grande como para contener todos los valores enumerados; no tiene por qué ser el tipo más pequeño posible. El tipo subyacente se puede especificar directamente, lo que permite las "declaraciones anticipadas" de enumeraciones:
// debe ajustarse en tamaño y disposición de memoria al tipo enum 'long' clase Color : long { ROJO , VERDE , AZUL , };// Declaración anticipada. Si posteriormente se definen valores // que no caben en 'char', se produce un error. enum class Shapes : char ;Conceptos
Los conceptos son una extensión de la funcionalidad de plantillas que ofrece el lenguaje de programación C++ . Los conceptos son predicados booleanos con nombre sobre los parámetros de la plantilla, que se evalúan en tiempo de compilación . Un concepto puede estar asociado a una plantilla ( plantilla de clase , plantilla de función , función miembro de una plantilla de clase, plantilla de variable o plantilla de alias ), en cuyo caso actúa como una restricción : limita el conjunto de argumentos que se aceptan como parámetros de la plantilla.
Los principales usos de los conceptos son:
- Introducción de la comprobación de tipos a la programación con plantillas
- Diagnóstico simplificado del compilador para instanciaciones de plantillas fallidas
- seleccionar sobrecargas de plantillas de funciones y especializaciones de plantillas de clases en función de las propiedades de tipo
- Restringir la deducción automática de tipos
Hay cinco lugares diferentes en la firma de una plantilla de función donde se puede usar una restricción (etiquetados a continuación del 1 al 5): [ 47 ]
plantilla < Concepto1 T > requiere Concepto2 < T > Concepto3 auto myFunction ( Concepto4 auto param ) requiere Concepto5 < T > ;Concept1: Una restricción de tipo. Este tipo reemplazaclassotypenamepara declarar un parámetro de plantilla de tipo. Cuando se utiliza un concepto en lugar de los dos anteriores, el tipo es restricción.Concept2: Una cláusula requires. Cuando una restricción de tipo no funciona, por ejemplo, porque el concepto toma múltiples parámetros, se puede usar una cláusula requires para aplicar restricciones más elaboradas.Concept3,Concept4: Un tipo de marcador de posición restringido. La misma sintaxis está disponible para la variable de marcador de posiciónauto, también conocida como variable. C++20 agregó plantillas de función abreviadas que usanautocomo tipo de marcador de posición en la declaración de parámetros. [ 48 ] Un tipo de marcador de posición restringido permite establecer restricciones en el tipo de retorno deducido automáticamente de una función o una variable.Concept5: Una cláusula requires final. Esta forma es similar aConcept2con una excepción notable. Una cláusula requires final se puede aplicar a una función en una plantilla de clase. Esto permite que la función siga siendo una función regular, sin plantilla, que se puede habilitar o deshabilitar según la cláusula requires final de la función.
Las restricciones se pueden configurar Concept1y Concept2utilizar en todo tipo de plantillas.
Inclusión de código
Encabezados
Tradicionalmente (antes de C++20 ), la inclusión de código en C++ seguía el método de C, en el que el código se importaba a otro archivo utilizando la directiva del preprocesador #include, que copiaba el contenido del archivo al otro archivo.
Tradicionalmente, el código C++ se dividía entre un archivo de cabecera (normalmente con extensión .h , .hpp o .hh ) y un archivo fuente (normalmente con extensión .cpp o .cc ). El archivo de cabecera solía contener las declaraciones de los símbolos, mientras que el archivo fuente contenía la implementación propiamente dicha, como las implementaciones de las funciones. Esta separación se imponía a menudo porque incluir código en otro archivo implicaba reprocesarlo para cada archivo que lo incluía, lo que aumentaba los tiempos de compilación si el compilador tenía que reprocesar el mismo código fuente repetidamente. #include
Los encabezados a menudo también obligaban al uso de directivas #include o #pragma once para evitar que un encabezado se incluyera potencialmente en un archivo varias veces.
La biblioteca estándar de C++ sigue siendo accesible a través de encabezados, sin embargo, desde C++23 también se ha hecho accesible mediante módulos. [ 49 ] [ 50 ] Incluso con la introducción de los módulos, los encabezados siguen desempeñando un papel en el C++ moderno, ya que las bases de código existentes no han migrado completamente a módulos.
Unidades de cabecera
Los encabezados se incluyen tradicionalmente mediante inclusión textual por el preprocesador usando #include, mientras que los módulos se incluyen durante la compilación a través de import. Sin embargo, los encabezados también se pueden importar usando import, incluso si no se declaran como módulos; estos se llaman "unidades de encabezado" y están diseñados para permitir que las bases de código existentes migren de encabezados a módulos de manera más gradual. [ 51 ] La sintaxis es similar a la inclusión de un encabezado, con la diferencia de que #includese reemplaza por importy se coloca un punto y coma al final de la instrucción. Las unidades de encabezado exportan automáticamente todos los símbolos y se diferencian de los módulos propiamente dichos en que permiten la emisión de macros, lo que significa que todos los que importen la unidad de encabezado obtendrán sus macros contenidas. Esto ofrece una ruptura mínima durante la migración a módulos. La semántica de búsqueda del archivo dependiendo de si se usan comillas o corchetes angulares también se aplica aquí. Por ejemplo, se puede escribir para importar el encabezado o para importar el archivo como una unidad de encabezado. La mayoría de los sistemas de compilación, como CMake , aún no admiten esta característica. import<string>;<string>import"MyHeader.h";"MyHeader.h"
Incrustaciones de recursos binarios
C++26 añade la #embeddirectiva de preprocesador para la inclusión de recursos binarios. Esta #embeddirectiva permite incrustar contenido binario en un archivo, incluso si no se trata de código C++ válido.
constexpr char ICON_DISPLAY_DATA [] = { #embed "art.png" };// especifique cualquier tipo que pueda inicializarse a partir de expresiones constantes enteras lo hará constexpr char RESET_BLOB [] = { #embed "data.bin" };// los atributos funcionan igual de bien alignas ( 8 ) constexpr char ALIGNED_DATA_STRING [] = { #embed "attributes.xml" };int main () { return #embed </dev/urandom> limit(1) ; }Módulos
Los módulos no utilizan el preprocesador de C en absoluto, sino que son gestionados directamente por el compilador. [ 49 ] Un módulo se declara usando , y el inicio del preámbulo del módulo comienza con . Los símbolos exportados que serán accesibles para las unidades de traducción que los importan están marcados con , y un módulo se importa a la unidad de traducción usando . Los módulos no exportan macros, debido a que se gestionan después del paso de preprocesamiento. exportmodulemodule;exportimport
Los módulos también pueden tener particiones, que no se pueden importar individualmente, sino que pertenecen a un módulo más grande.
Ejemplo
Un ejemplo sencillo de uso de módulos es el siguiente:
MiClase.cppm
exportar módulo wikipedia . proyecto . Persona ;importar std ;usando std :: string ;exportar espacio de nombres wikipedia :: proyecto {clase Persona { privado : cadena nombre ; entero edad ; público : Persona ( cadena nombre , entero edad ) : nombre { std :: move ( nombre ) }, edad { edad } {}string getName () const { return name ; }void setName ( string name ) { this -> name = std :: move ( val ); }int getAge () const noexcept { return age ; }void setAge ( int age ) noexcept { this -> age = age ; }; };}Main.cpp
importar std ;importar wikipedia.proyecto.Persona ;usando wikipedia :: proyecto :: Persona ;int main ( int argc , char * argv []) { Person me ( "John Doe" , 10 ); me.setAge ( 15 ) ; std :: println ( " ¡Hola, {0}! {0} tiene {1} años.", me.getName(), me.getAge ( ) ) ; // imprime " ¡ Hola , John Doe! John Doe tiene 15 años." }Atributos
Desde C++11, C++ ha admitido secuencias de especificadores de atributos. [ 52 ] Los atributos se pueden aplicar a cualquier símbolo que los admita, incluidas clases, funciones/métodos y variables, y cualquier símbolo marcado con un atributo será tratado específicamente por el compilador como necesario. Estos pueden considerarse similares a las anotaciones de Java para proporcionar información adicional al compilador; sin embargo, difieren en que los atributos en C++ no son metadatos que se pretenden acceder mediante reflexión. C++26 agrega soporte para anotaciones para reflexión. Además, no se pueden crear atributos personalizados en C++, a diferencia de Java, donde se pueden definir anotaciones personalizadas además de las estándar. Sin embargo, C++ tiene atributos específicos de implementación/proveedor que no son estándar. Estos suelen tener un espacio de nombres asociado. Por ejemplo, GCC y Clang tienen atributos bajo el gnu::espacio de nombres, y todos esos atributos tienen la forma . [[gnu::*]]
Se pueden aplicar varios atributos como una lista, por ejemplo (donde , , y son atributos). Además, los atributos también pueden aceptar argumentos, como . El siguiente es un ejemplo del uso de algunos atributos en C++. [[A,B,C]]ABC[[A("This is a parameter")]]
clase Entero { privado : // indica que x no necesita tener una // dirección única de otros miembros no estáticos [[ no_unique_address ]] int x ; público : // emite una advertencia durante la compilación si // el valor se descarta/ignora [[ nodiscard ]] bool isPositive () const noexcept { // indica que esta ruta es más probable if ( x > 0 ) [[ likely ]] { return true ; } return false ; } };Atributos estándar
El estándar C++ define los siguientes atributos:
Leyenda: : Obsoleto : Eliminado
Atributos con alcance
Como se mencionó anteriormente, GCC y Clang tienen atributos con ámbito (espacio de nombres), como , , y . Para aplicar varios atributos con ámbito, se puede escribir: [[gnu::always_inline]][[gnu::hot]][[gnu::const]]
[[ nodiscard ]] [[ gnu :: always_inline ]] [[ gnu :: const ]] [[ gnu :: hot ]] inline int f (); // declara f con cuatro atributos [[ nodiscard , gnu :: always_inline , gnu :: const , gnu :: hot ]] int f (); // igual que arriba, pero usa un único especificador attr que contiene cuatro atributos // C++17: [[ nodiscard ]] [[ using gnu : always_inline , const , hot ]] int f [[ gnu :: always_inline ]](); // un atributo puede aparecer en varios especificadoresReflexión
Además de la metaprogramación básica proporcionada en el encabezado <type_traits>, C++26 introduce la reflexión en tiempo de compilación. Se puede acceder a las capacidades de reflexión en tiempo de compilación en el encabezado <meta>y las declaraciones se almacenan en el espacio de nombres std::meta.
Anotaciones
La mayoría de las declaraciones pueden tener anotaciones adjuntas, que son simplemente valores asociados a esa declaración. [ 53 ] Al igual que las anotaciones de Java , se puede acceder a las anotaciones mediante reflexión. Las anotaciones se diferencian de los atributos, ya que estos últimos son principalmente un medio para comunicar información al compilador, mientras que las anotaciones son una característica de la reflexión y permiten adjuntar constantes y metadatos arbitrarios, lo que las hace personalizables para los programas, a diferencia de los atributos. Esto permite establecer un puente entre la API de la biblioteca y el usuario. Considere este ejemplo, donde algunas anotaciones se encuentran en el espacio de nombres wikipedia::examples:
importar std ;usando std :: string ;using wikipedia :: examples :: Debug ; using wikipedia :: examples :: EnumFlag ; using wikipedia :: examples :: Rename ;clase enum [[ = EnumFlag ()]] Toggle : uint8_t { OFF , ON , };struct [[ = Debug ()]] Person { [[ = Rename ( "Nombre completo" )]] string fullName ; int age ; };Las anotaciones no tienen ningún significado inicial a menos que algunas implementaciones las utilicen para identificar ciertas características y funcionalidades.
La creación de una anotación para generar una especialización std::formatter<T>es la siguiente:
using std :: formatter ; using std :: meta :: has_annotation ;plantilla < auto V > struct Derived {};plantilla < auto V > inline constexpr Derivado < V > Derivar ;inline constexpr struct {} Depurar ;plantilla < typename T > requiere ( has_annotation ( ^^ T , Derive < Debug > )) struct formatter < T > { // ... };struct [[ = Derive < Debug > ]] Point { int x ; int y ; };int main () { Point p = Point { .x = 1 , .y = 2 }; // imprime p = Point{ .x = 1, .y = 2 } std::println( " p = { }" , p ) ; }Véase también
Notas
- La macro ^
__COUNTER__no es una característica estándar de C++ . [ 3 ] - ^ Tenga en cuenta que esta función se eliminó en C++17 y el nombre se reintrodujo posteriormente en C++23 como unaclase de tipo de resultado
std::unexpected<T, E>.
Referencias
- ^ "std::println" . cppreference.com .
- ^ cppreference.com (2025). "Palabras clave de C++" . Consultado el 26 de febrero de 2025 .
- ^ Macros predefinidas , learn.microsoft.com. 25 de abril de 2026.
- ^ISO/IEC. Programming Languages – C++11 Draft (n3797)Archived 2 October 2018 at the Wayback Machine §3.7 Storage duration [basic.stc]
- ^ISO/IEC. Programming Languages – C++11 Draft (n3797)Archived 2 October 2018 at the Wayback Machine §3.7.1 Static Storage duration [basic.stc.static]
- ^ISO/IEC. Programming Languages – C++11 Draft (n3797)Archived 2 October 2018 at the Wayback Machine §3.7.2 Thread Storage duration [basic.stc.thread]
- ^ISO/IEC. Programming Languages – C++11 Draft (n3797)Archived 2 October 2018 at the Wayback Machine §3.7.3 Automatic Storage duration [basic.stc.auto]
- ^ISO/IEC. Programming Languages – C++11 Draft (n3797)Archived 2 October 2018 at the Wayback Machine §3.7.4 Dynamic Storage duration [basic.stc.dynamic]
- ^"C++ Core Guidelines". isocpp.github.io. Archived from the original on 8 February 2020. Retrieved 2020-02-09.
- ^"Bjarne Stroustrup's FAQ – Is C a subset of C++?". Archived from the original on 6 February 2016. Retrieved 5 May 2014.
- ^"C9X – The New C Standard". Archived from the original on 21 June 2018. Retrieved 27 December 2008.
- ^"C++0x Support in GCC". Archived from the original on 21 July 2010. Retrieved 12 October 2010.
- ^"C++0x Core Language Features In VC10: The Table". Archived from the original on 21 August 2010. Retrieved 12 October 2010.
- ^"Clang - C++98, C++11, and C++14 Status". Clang.llvm.org. 12 May 2013. Archived from the original on 4 July 2013. Retrieved 10 June 2013.
- ^Bokil, Milind A. (2021). "Writing Assembly Routines within C/C++ and Java Programs". ResearchGate. Retrieved April 1, 2025.
- ^ De Vilhena, Paulo Emílio; Lahav, Ori; Vafeiadis, Viktor; Raad, Azalea (2024). "Extending the C/C++ Memory Model with Inline Assembly" . Proceedings of the ACM on Programming Languages . 8 : 1081–1107 . arXiv : 2408.17208 . doi : 10.1145/3689749 .
- ^ Colaboradores de cppreference.com. " Declaración asm ". cppreference.com . Consultado el 1 de abril de 2025.
- ^ "Ensamblador extendido (usando la colección de compiladores GNU)" . Documentación en línea de GCC . Proyecto GNU . Consultado el 1 de abril de 2025 .
- ^ Intel Corporation. " Ensamblador en línea ". Guía y referencia para desarrolladores del compilador Intel® C++ Classic , versión 2021.9. Consultado el 1 de abril de 2025.
- ^ IBM. " Instrucciones de ensamblador en línea (extensión de IBM) ". Documentación de IBM . Consultado el 1 de abril de 2025.
- ^ "Descripción general del ensamblador en línea" . Microsoft Learn . Microsoft . Consultado el 1 de abril de 2025 .
- ^ "Interfaz entre C y C++ y lenguaje ensamblador" . Texas Instruments . Texas Instruments Incorporated. 23 de febrero de 2025. Consultado el 1 de abril de 2025 .
- ^ "Enlace de C++ a ASM en GCC" . OSDev Wiki . Consultado el 1 de abril de 2025 .
- ^ Sutter, Herb ; Alexandrescu, Andrei (2004). Estándares de codificación de C++: 101 reglas, directrices y mejores prácticas . Addison-Wesley.
- ^ Henricson, Mats; Nyquist, Erik (1997). Industrial Strength C++ . Prentice Hall. ISBN 0-13-120965-5.
- ^ Stroustrup, Bjarne (2000). El lenguaje de programación C++ (Edición especial). Addison-Wesley. pág. 310. ISBN 0-201-70073-5.
Una función miembro virtual a veces se denomina método .
- ^ ab"Nobody Understands C++: Part 5: Template Code Bloat". articles.emptycrate.com/: EmptyCrate Software. Travel. Stuff. 6 May 2008. Archived from the original on 25 April 2016. Retrieved 8 March 2010.
On occasion you will read or hear someone talking about C++ templates causing code bloat. I was thinking about it the other day and thought to myself, "self, if the code does exactly the same thing then the compiled code cannot really be any bigger, can it?" [...] And what about compiled code size? Each were compiled with the command g++ <filename>.cpp -O3. Non-template version: 8140 bytes, template version: 8028 bytes!
- ^Joshua Berne, Timur Doumler, Andrzej Krzemieński (13 February 2025). "Contracts for C++"(PDF). open-std.org. WG 22.
{{cite web}}: CS1 maint: varios nombres: lista de autores ( enlace ) - ^ ab"Contract assertions (since C++26)". cppreference.com. cppreference. Retrieved 9 November 2025.
- ^cppreference.com. "Function declaration". cppreference.com. cppreference.com. Retrieved 28 May 2026.
- ^cppreference.com. "Standard library header <contracts> (C++26)". cppreference.com. cppreference.com. Retrieved 28 May 2026.
- ^cppreference.com. "Coroutines (C++20)". cppreference.com. cppreference.com. Retrieved 30 May 2026.
- ^Dietmar Kühl, Maikel Nadolski (18 May 2025). "Add a Coroutine Task Type"(PDF). open-std.org. WG21.
- ^cppreference.com. "std::execution::sync_wait". cppreference.com. cppreference.com. Retrieved 15 February 2026.
- ^Mycroft, Alan (2013). "C and C++ Exceptions | Templates"(PDF). Cambridge Computer Laboratory - Course Materials 2013-14. Archived(PDF) from the original on 13 May 2016. Retrieved 30 August 2016.
- ^Stroustrup, Bjarne (2013). The C++ Programming Language. Addison Wesley. p. 345. ISBN 9780321563842.
- ^Stroustrup, Bjarne (2013). The C++ Programming Language. Addison Wesley. pp. 363–365. ISBN 9780321563842.
- ^Stroustrup, Bjarne (2013). The C++ Programming Language. Addison Wesley. pp. 345, 363. ISBN 9780321563842.
- ^"Google C++ Style Guide". Archived from the original on 16 March 2019. Retrieved 25 June 2019.
- ^"LLVM Coding Standards". LLVM 9 documentation. Archived from the original on 27 June 2019. Retrieved 25 June 2019.
- ^"Coding Conventions". Qt Wiki. Archived from the original on 26 June 2019. Retrieved 26 June 2019.
- ^Stroustrup, Bjarne (2013). The C++ Programming Language. Addison Wesley. p. 349. ISBN 9780321563842.
- ^Stroustrup, Bjarne (2013). The C++ Programming Language. Addison Wesley. pp. 344, 370. ISBN 9780321563842.
- ^cppreference.com. "std::meta::exception". cppreference.com. cppreference.com. Retrieved 19 June 2026.
- ^cppreference.com. "std::uncaught_exception, std::uncaught_exceptions". cppreference.com. cppreference.com. Retrieved 19 June 2026.
- ^Bjarne Stroustrup, The C++ Programming Language Third Edition, Addison Wesley, 1997. ISBN 0-201-88954-4. pp. 375-380.
- ^Fertig, Andreas (2021). Programming with C++20. Fertig Publications. p. 23. ISBN 978-3-949323-01-0.
- ^"ISO/IEC 14882:2020". ISO. December 2020. Retrieved 14 July 2022.
- ^ abcppreference.com (2025). "Modules (since C++20)". Retrieved 2025-02-20.
- ^cppreference.com (2025). "C++ Standard Library". Retrieved 2025-02-20.
- ^"Walkthrough: Build and import header units in Microsoft Visual C++". Microsoft. 12 April 2022.
- ^"Attribute specifier sequence (since C++11)". cppreference.com. Retrieved 6 June 2025.
- ^ Wyatt Childers, Dan Katz, Barry Revzin, Daveed Vandevoorde (14 de octubre de 2024). "Anotaciones para la reflexión" . ISO/IEC JTC1/SC22/WG21 - Estándares de C++ . ISO/IEC . Consultado el 4 de septiembre de 2025 .
{{cite web}}: CS1 maint: varios nombres: lista de autores ( enlace )
- C++
- Código fuente
- sintaxis del lenguaje de programación