C++11 es una versión de la norma técnica conjunta ISO/IEC 14882, de la Organización Internacional de Normalización (ISO) y la Comisión Electrotécnica Internacional (IEC), para el lenguaje de programación C++ . C++11 reemplazó la versión anterior del estándar C++, denominada C++03 , [ 1 ] y posteriormente fue reemplazada por C++14 . El nombre sigue la tradición de nombrar las versiones del lenguaje según el año de publicación de la especificación, aunque anteriormente se denominaba C++0x porque se esperaba su publicación antes de 2010. [ 2 ]
Aunque uno de los objetivos de diseño era priorizar los cambios en las bibliotecas sobre los cambios en el lenguaje principal , [ 3 ] C++11 sí introduce varias adiciones al lenguaje principal. Las áreas del lenguaje principal que se mejoraron significativamente incluyen la compatibilidad con multihilo, la compatibilidad con programación genérica , la inicialización uniforme y el rendimiento. También se realizaron cambios significativos en la biblioteca estándar de C++ , incorporando la mayoría de las bibliotecas del Informe Técnico 1 (TR1) de C++ , excepto la biblioteca de funciones especiales matemáticas. [ 4 ]
C++11 se publicó como ISO/IEC 14882:2011 [ 5 ] en septiembre de 2011 y está disponible previo pago. El borrador de trabajo más similar al estándar C++11 publicado es N3337, con fecha del 16 de enero de 2012; [ 6 ] solo contiene correcciones editoriales del estándar C++11. [ 7 ]
C++11 fue totalmente compatible con Clang 3.3 y versiones posteriores, [ 8 ] y con GNU Compiler Collection (GCC) 4.8.1 y versiones posteriores. [ 9 ]
Objetivos de diseño
El comité de diseño intentó ceñirse a una serie de objetivos al diseñar C++11:
- Mantener la estabilidad y la compatibilidad con el código anterior.
- Prefiero introducir nuevas características a través de la biblioteca estándar, en lugar de extender el lenguaje principal.
- Mejorar C++ para facilitar el diseño de sistemas y bibliotecas, en lugar de introducir nuevas características útiles solo para aplicaciones específicas.
- Aumentar la seguridad tipográfica proporcionando alternativas más seguras a las técnicas anteriores que eran inseguras.
- Aumenta el rendimiento y la capacidad de trabajar directamente con el hardware.
- Proporcionar soluciones adecuadas para problemas del mundo real.
- Facilitar la enseñanza y el aprendizaje de C++ sin eliminar ninguna utilidad necesaria para los programadores expertos.
Se considera importante prestar atención a los principiantes, porque la mayoría de los programadores informáticos siempre lo serán, y porque muchos principiantes nunca amplían sus conocimientos, limitándose a trabajar en aspectos del lenguaje en el que se especializan. [ 2 ] [ 3 ]
Extensiones al lenguaje central de C++
Una de las funciones del comité de C++ es el desarrollo del núcleo del lenguaje. Entre las áreas del núcleo del lenguaje que se mejoraron significativamente se incluyen la compatibilidad con multihilo , la compatibilidad con programación genérica , la inicialización uniforme y el rendimiento.
Mejoras en el rendimiento del entorno de ejecución del lenguaje principal
Estas características del lenguaje existen principalmente para proporcionar algún tipo de ventaja en el rendimiento durante la ejecución , ya sea en cuanto a la memoria o a la velocidad de procesamiento.
Referencias a Rvalue y constructores de movimiento
En C++03 (y anteriores), los temporarios (denominados " rvalues ", ya que a menudo se encuentran en el lado derecho de una asignación) estaban destinados a no ser nunca modificables —al igual que en C— y se consideraban indistinguibles de los tipos; sin embargo, en algunos casos, los temporarios podían modificarse, un comportamiento que incluso se consideraba una laguna útil. [ 10 ] C++11 agrega un nuevo tipo de referencia no constante llamadoconst T&Referencia de rvalue , identificada por. Esto se refiere a los temporales que se pueden modificar después de su inicialización, con el fin de permitir la "semántica de movimiento".T&&
Un problema crónico de rendimiento en C++03 son las costosas e innecesarias copias profundas que pueden ocurrir implícitamente cuando los objetos se pasan por valor. Para ilustrar el problema, consideremos que un objeto temporal std::vector<T>es, internamente, un contenedor para un array de estilo C con un tamaño definido. Si std::vector<T>se crea o se devuelve un objeto temporal desde una función, solo se puede almacenar creando un nuevo objeto std::vector<T>temporal y copiando todos los datos del objeto temporal en él. Entonces, el objeto temporal y toda su memoria se destruyen. (Para simplificar, esta explicación omite la optimización del valor de retorno ).
En C++11, unEl constructor move questd::vector<T>toma una referencia rvalue a unstd::vector<T>puede copiar el puntero al array interno de estilo C desde el rvalue al nuevostd::vector<T>, luego establecer el puntero dentro del rvalue a null. Dado que el temporal nunca se volverá a usar, ningún código intentará acceder al puntero null, y debido a que el puntero es null, su memoria no se borra cuando sale del ámbito. Por lo tanto, la operación no solo evita el costo de una copia profunda, sino que es segura e invisible.
Las referencias a Rvalue pueden proporcionar mejoras de rendimiento al código existente sin necesidad de realizar cambios fuera de la biblioteca estándar. std::vector<T>No es necesario cambiar explícitamente el tipo del valor devuelto por una función que devuelve un objeto temporal para std::vector<T>&&invocar el constructor de movimiento, ya que los objetos temporales se consideran Rvalue automáticamente. (Sin embargo, si std::vector<T>se trata de una versión de C++03 sin constructor de movimiento, se invocará el constructor de copia con un objeto temporal const std::vector<T>, lo que conlleva una asignación de memoria significativa).
Por motivos de seguridad, se imponen algunas restricciones. Una variable con nombre nunca se considerará un rvalue, aunque se declare como tal. Para obtener un rvalue, std::move()se debe usar la plantilla de función. Las referencias a rvalue solo se pueden modificar bajo ciertas circunstancias, y están diseñadas principalmente para usarse con constructores de movimiento.
Debido a la naturaleza de la redacción de las referencias a rvalues y a algunas modificaciones en la redacción de las referencias a lvalues (referencias regulares), las referencias a rvalues permiten a los desarrolladores proporcionar un reenvío de funciones perfecto. Al combinarse con plantillas variádicas , esta capacidad permite crear plantillas de funciones que pueden reenviar argumentos perfectamente a otra función que acepte esos argumentos específicos. Esto resulta especialmente útil para reenviar parámetros de constructores, creando funciones de fábrica que llamarán automáticamente al constructor correcto para esos argumentos específicos. Esto se observa en el conjunto de métodos `emplace_back` de la biblioteca estándar de C++.
constexpr – Expresiones constantes generalizadas
C++ siempre ha contado con el concepto de expresiones constantes. Estas expresiones, como por 3 + 4ejemplo, siempre producen los mismos resultados, tanto en tiempo de compilación como en tiempo de ejecución. Las expresiones constantes representan oportunidades de optimización para los compiladores, quienes frecuentemente las ejecutan en tiempo de compilación e incorporan los resultados directamente en el programa. Además, en varios apartados, la especificación de C++ exige el uso de expresiones constantes. La definición de un array requiere una expresión constante, y los valores de los enumeradores también deben ser expresiones constantes.
Sin embargo, nunca se ha permitido que una expresión constante contenga una llamada a una función o un constructor de objeto. Por lo tanto, un fragmento de código tan simple como este no es válido:
int obtenerCinco () { return 5 ; }int someValue [ getFive () + 7 ]; // Crea un array de 12 enteros. C++ mal formadoEsto no era válido en C++03, porque getFive() + 7no es una expresión constante. Un compilador de C++03 no tiene forma de saber si getFive()realmente es constante en tiempo de ejecución. En teoría, esta función podría afectar a una variable global, llamar a otras funciones que no son constantes en tiempo de ejecución, etc.
C++11 introdujo la palabra clave constexpr, que permite al usuario garantizar que un constructor de función u objeto sea una constante en tiempo de compilación. [ 11 ] El ejemplo anterior se puede reescribir de la siguiente manera:
constexpr int getFive () { return 5 ; }int someValue [ getFive () + 7 ]; // Crea un array de 12 enteros. Válido en C++11Esto permite que el compilador entienda y verifique que getFive()se trata de una constante en tiempo de compilación.
El uso constexprde `void` en una función impone ciertas limitaciones. Primero, la función debe tener un tipo de retorno distinto de `void`. Segundo, el cuerpo de la función no puede declarar variables ni definir nuevos tipos. Tercero, el cuerpo solo puede contener declaraciones, sentencias `null` y una única sentencia `return`. Deben existir valores para los argumentos tales que, tras la sustitución de argumentos, la expresión en la sentencia `return` produzca una expresión constante.
Antes de C++11, los valores de las variables solo podían usarse en expresiones constantes si las variables se declaraban como const, tenían un inicializador que era una expresión constante y eran de tipo entero o enumeración. C++11 elimina la restricción de que las variables deban ser de tipo entero o enumeración si se definen con la constexprpalabra clave:
constexpr double ACELERACIÓN_GRAVITACIONAL_TIERRA = 9.8 ; constexpr double ACELERACIÓN_GRAVITACIONAL_LUNA = ACELERACIÓN_GRAVITACIONAL_TIERRA / 6.0 ;Dichas variables de datos son implícitamente constantes y deben tener un inicializador que debe ser una expresión constante.
Para construir valores de datos de expresiones constantes a partir de tipos definidos por el usuario, los constructores también pueden declararse con constexpr. constexprEl cuerpo de la función de un constructor solo puede contener declaraciones y sentencias nulas, y no puede declarar variables ni definir tipos, como ocurre con una constexprfunción. Deben existir valores de argumento tales que, tras la sustitución de argumentos, inicialice los miembros de la clase con expresiones constantes. Los destructores para dichos tipos deben ser triviales.
El constructor de copia para un tipo con constexprconstructores también debería definirse como un constexprconstructor, para permitir que los objetos de ese tipo sean devueltos por valor desde una función `constexpr`. Cualquier función miembro de una clase, como constructores de copia, sobrecargas de operadores, etc., puede declararse como `constexpr` constexpr, siempre que cumpla con los requisitos para funciones `constexpr`. Esto permite al compilador copiar objetos en tiempo de compilación, realizar operaciones sobre ellos, etc.
Si se llama a una función o constructor de tipo constexpr con argumentos que no son expresiones constantes, la llamada se comporta como si la función no fuera constexpr, y el valor resultante no es una expresión constante. Del mismo modo, si la expresión en la instrucción return de una función constexpr no se evalúa como una expresión constante en una invocación determinada, el resultado no es una expresión constante.
constexprdifiere de consteval, introducido en C++20 , en que este último siempre debe producir una constante en tiempo de compilación, mientras que constexprno tiene esta restricción.
Modificación de la definición de datos simples y antiguos
En C++03, una clase o estructura debe cumplir una serie de reglas para ser considerada un tipo de datos simple (POD). Los tipos que cumplen esta definición generan diseños de objetos compatibles con C y pueden inicializarse estáticamente. El estándar C++03 impone restricciones sobre qué tipos son compatibles con C o pueden inicializarse estáticamente, a pesar de que no exista ninguna razón técnica que impida que un compilador acepte el programa. Si alguien creara un tipo POD de C++03 y le añadiera una función miembro virtual, este tipo dejaría de ser un tipo POD, no podría inicializarse estáticamente y sería incompatible con C, aunque no se modificara la distribución de la memoria.
C++11 relajó varias de las reglas POD, dividiendo el concepto POD en dos conceptos separados: trivial y diseño estándar .
Un tipo trivial puede inicializarse estáticamente. Esto también significa que es válido copiar datos mediante `copy` std::memcpy(), en lugar de tener que usar un constructor de copia. La vida útil de un tipo trivial comienza cuando se define su almacenamiento, no cuando finaliza un constructor.
Una clase o estructura trivial se define como aquella que:
- Tiene un constructor predeterminado trivial. Este puede usar la sintaxis del constructor predeterminado (
X() = default;). - Tiene constructores de copia y movimiento sencillos, que pueden usar la sintaxis predeterminada.
- Dispone de operadores de asignación de copia y movimiento triviales, que pueden utilizar la sintaxis predeterminada.
- Tiene un destructor trivial, que no debe ser virtual.
Los constructores son triviales solo si no hay funciones miembro virtuales de la clase ni clases base virtuales. Las operaciones de copia/movimiento también requieren que todos los miembros de datos no estáticos sean triviales.
Un tipo que tiene un diseño estándar significa que ordena y empaqueta sus miembros de una manera compatible con C. Una clase o estructura tiene un diseño estándar, por definición, siempre que:
- No tiene funciones virtuales
- No tiene clases base virtuales
- Todos sus miembros de datos no estáticos tienen el mismo control de acceso (público, privado, protegido).
- Todos sus miembros de datos no estáticos, incluidos los de sus clases base, se encuentran en la misma clase dentro de la jerarquía.
- Las reglas anteriores también se aplican a todas las clases base y a todos los miembros de datos no estáticos en la jerarquía de clases.
- No tiene clases base del mismo tipo que el primer miembro de datos no estático definido.
Una clase/estructura/unión se considera un POD si es trivial, tiene un diseño estándar y todos sus miembros de datos no estáticos y clases base son POD.
Al separar estos conceptos, es posible prescindir de uno sin perder el otro. Una clase con constructores de copia y movimiento complejos puede no ser trivial, pero podría tener una estructura estándar y, por lo tanto, ser interoperable con C. De manera similar, una clase con miembros de datos públicos y privados no estáticos no tendría una estructura estándar, pero podría ser trivial y, por lo tanto, memcpycompatible.
Mejoras de rendimiento en tiempo de compilación del lenguaje principal
Plantilla externa
En C++03, el compilador debe instanciar una plantilla siempre que encuentre una plantilla completamente especificada en una unidad de traducción. Si la plantilla se instancia con los mismos tipos en varias unidades de traducción, esto puede aumentar drásticamente los tiempos de compilación. No hay forma de evitar esto en C++03, por lo que C++11 introdujo las declaraciones de plantilla extern, análogas a las declaraciones de datos extern.
C++03 tiene esta sintaxis para obligar al compilador a instanciar una plantilla:
clase plantilla std :: vector < X > ;C++11 ahora proporciona esta sintaxis:
extern template class std :: vector < X > ;lo que le indica al compilador que no instancie la plantilla en esta unidad de traducción.
Mejoras en la usabilidad del idioma principal
Estas características existen principalmente para facilitar el uso del lenguaje. Pueden mejorar la seguridad de tipos, minimizar la repetición de código, reducir la probabilidad de código erróneo, etc.
Listas de inicialización
C++03 heredó la característica de lista de inicialización de C. A una estructura o arreglo se le pasa una lista de argumentos entre llaves, en el orden en que se definen sus miembros dentro de la estructura. Estas listas de inicialización son recursivas, por lo que un arreglo de estructuras o una estructura que contenga otras estructuras puede utilizarlas.
struct FloatAndInt { float first ; int second ; };FloatAndInt scalar = { 0.43f , 10 }; // Un objeto, con first=0.43f y second=10 FloatAndInt a [] = {{ 13.4f , 3 }, { 43.28f , 29 }, { 5.934f , 17 }}; // Un array de tres objetosEsto es muy útil para listas estáticas o para inicializar una estructura con algún valor. C++ también proporciona constructores para inicializar un objeto, pero a menudo no son tan convenientes como la lista de inicialización. Sin embargo, C++03 permite listas de inicialización solo en estructuras y clases que se ajustan a la definición de Plain Old Data (POD); C++11 extiende las listas de inicialización, por lo que se pueden usar para todas las clases, incluidos los contenedores estándar como std::vector<T>.
C++11 vincula el concepto a una plantilla, llamada std::initializer_list<T>. Esto permite que los constructores y otras funciones tomen listas de inicialización como parámetros. Por ejemplo:
usando std :: initializer_list ;clase MySequence { público : MySequence ( lista_inicializador <int> lista ) ; } ;Esto permite MySequenceconstruir a partir de una secuencia de números enteros, como por ejemplo:
MiSecuencia s = { 1 , 4 , 5 , 6 };Este constructor es un tipo especial de constructor, llamado constructor de lista de inicialización. Las clases con este constructor reciben un tratamiento especial durante la inicialización uniforme (véase más abajo ).
La clase plantilla std::initializer_list<T>es un tipo de primera clase de la biblioteca estándar de C++11. Puede construirse estáticamente mediante el compilador de C++11 utilizando la {}sintaxis sin nombre de tipo en contextos donde dichas llaves deducirán un std::initializer_list<T>, o especificando explícitamente el tipo como std::initializer_list<T>{args}(y así sucesivamente para otras variedades de sintaxis de construcción).
La lista se puede copiar una vez construida, lo cual es económico y funciona como una copia por referencia (la clase generalmente se implementa como un par de punteros de inicio/fin). Una lista std::initializer_list<T>es constante: sus miembros no se pueden modificar una vez creada, ni tampoco los datos que contienen (lo que descarta la posibilidad de mover datos desde ellos, lo que requeriría copias en miembros de la clase, etc.).
Aunque su construcción es tratada de forma especial por el compilador, std::initializer_list<T>es un tipo real, por lo que puede utilizarse en otros lugares además de los constructores de clases. Las funciones regulares pueden tomar std::initializer_list<T>argumentos tipados. Por ejemplo:
usando std :: initializer_list ;void foo ( initializer_list < float > list ); // Copiar es barato; ver arribafoo ({ 1.0f , -3.45f , -0.4f });Ejemplos de esto en la biblioteca estándar incluyen las plantillas std::min()y que toman valores de tipo numérico.std::max()std::initializer_list<T>
Los contenedores estándar también se pueden inicializar de estas maneras:
using std :: string ; using std :: vector ;vector < string > v = { "xyzzy" , "plugh" , "abracadabra" }; vector < string > v ({ "xyzzy" , "plugh" , "abracadabra" }); vector < string > v { "xyzzy" , "plugh" , "abracadabra" }; // ver "Inicialización uniforme" a continuaciónInicialización uniforme
C++03 tiene varios problemas con la inicialización de tipos. Existen varias formas de hacerlo, y algunas producen resultados diferentes al intercambiarlas. La sintaxis tradicional del constructor, por ejemplo, puede parecer una declaración de función, y se deben tomar medidas para asegurar que la regla de análisis sintáctico más problemática del compilador no la confunda con tal. Solo los agregados y los tipos POD se pueden inicializar con inicializadores de agregados (usando ).Tx={/* initialization */};
C++11 proporciona una sintaxis que permite una inicialización de tipos totalmente uniforme que funciona en cualquier objeto. Amplía la sintaxis de la lista de inicializadores:
struct IntAndFloat1 { int x ; double y ; };struct IntAndFloat2 { private : int x ; double y ; public : IntAndFloat2 ( int x , double y ) : x { x }, y { y } {} };IntAndFloat1 v1 { 5 , 3.2 }; IntAndFloat2 v2 { 2 , 4.3 };La inicialización v1se comporta exactamente como una inicialización agregada. Es decir, cada miembro de datos de un objeto se inicializará por copia con el valor correspondiente de la lista de inicialización. Se utilizará la conversión de tipo implícita donde sea necesario. Si no existe ninguna conversión, o solo existe una conversión de restricción, el programa está mal formado. La inicialización v2invoca al constructor.
También se puede hacer esto:
usando std :: string ;struct IdString { string nombre ; int identificador ; };IdString getString () { return { "foo" , 42 }; // Nótese la falta de tipo explícito. }La inicialización uniforme no reemplaza la sintaxis del constructor, que aún se necesita en ocasiones. Si una clase tiene un constructor de lista de inicialización ( ), entonces tiene prioridad sobre otras formas de construcción, siempre que la lista de inicialización se ajuste al tipo del constructor de secuencia. La versión C++11 de tiene un constructor de lista de inicialización para su tipo de plantilla. Por lo tanto, este código:T(initializer_list<X>v);std::vector<T>
std :: vector <int> vec { 4 } ;Se llamará al constructor de la lista de inicialización, no al constructor std::vectorque toma un único parámetro de tamaño y crea el vector con ese tamaño. Para acceder a este último constructor, el usuario deberá usar directamente la sintaxis estándar del constructor.
Inferencia de tipo
En C++03 (y C), para usar una variable, su tipo debe especificarse explícitamente. Sin embargo, con la llegada de los tipos de plantilla y las técnicas de metaprogramación con plantillas, el tipo de un elemento, en particular el valor de retorno bien definido de una función, puede no ser fácil de expresar. Por lo tanto, almacenar valores intermedios en variables resulta difícil, y posiblemente requiera conocer el funcionamiento interno de la biblioteca de metaprogramación en cuestión.
C++11 permite mitigar esto de dos maneras. Primero, la definición de una variable con una inicialización explícita puede usar la autopalabra clave. [ 12 ] [ 13 ] Esto crea una variable del tipo específico del inicializador:
usando el espacio de nombres std :: placeholders ;auto callable = std :: bind ( & foo , _2 , _1 , x ); auto y = 5 ;El tipo de callablees simplemente lo que devuelve la anulación de la función plantilla particular std::bind()para esos argumentos específicos. Este tipo lo determina fácilmente el compilador de forma procedimental como parte de sus tareas de análisis semántico, pero no es fácil de determinar para el usuario mediante inspección. El tipo de ytambién está bien definido, pero es más fácil de determinar para el usuario. Es un int, que es del mismo tipo que el literal entero.
Este uso de la palabra clave autoen C++ reutiliza la semántica de dicha palabra clave, que originalmente se utilizaba en el lenguaje predecesor sin tipado B con una función relacionada: la de denotar una definición automática de variable sin tipado .
Además, la palabra clave decltypese puede utilizar para determinar el tipo de expresión en tiempo de compilación. Por ejemplo:
int i = 0 ; decltype ( i ) j = 5 ;Esto resulta más útil en combinación con auto, ya que el tipo de variable automática solo lo conoce el compilador. Sin embargo, decltypetambién puede ser muy útil para expresiones en código que utiliza intensivamente la sobrecarga de operadores y tipos especializados.
autoTambién es útil para reducir la verbosidad del código. Por ejemplo, en lugar de escribir
usando std :: vector ;for ( vector < int >:: const_iterator it = v . cbegin (); it != v . cend (); ++ it ) { /* bucle */ }El programador puede utilizar la versión más corta.
for ( auto it = v . cbegin (); it != v . cend (); ++ it ) { /* bucle */ }que se puede compactar aún más ya que vimplementa begin()/ end()iteradores:
for ( const auto & x : v ) { /* bucle */ }Esta diferencia aumenta a medida que el programador comienza a anidar contenedores, aunque en tales casos typedefse pueden utilizar para disminuir la cantidad de código.
El tipo denotado por decltypepuede ser diferente del tipo deducido por auto.
#include <vector>usando std :: vector ;int main () { const vector < int > v ( 1 ); auto a = v [ 0 ]; // a tiene tipo int decltype ( v [ 0 ]) b = 1 ; // b tiene tipo const int&, el tipo de retorno de // std::vector<int>::operator[](size_type) const auto c = 0 ; // c tiene tipo int auto d = c ; // d tiene tipo int decltype ( c ) e ; // e tiene tipo int, el tipo de la entidad nombrada por c decltype (( c )) f = c ; // f tiene tipo int&, porque (c) es un lvalue decltype ( 0 ) g ; // g tiene tipo int, porque 0 es un rvalue }Bucle for basado en rangos
C++11 amplía la sintaxis de la forinstrucción para permitir una fácil iteración sobre un rango de elementos:
int a [ 5 ] = { 1 , 2 , 3 , 4 , 5 }; // duplica el valor de cada elemento en a: for ( int & x : a ) { x *= 2 ; }// similar pero también usando inferencia de tipos para elementos de matriz for ( auto & x : a ) { x *= 2 ; }Esta forma de iterar for, denominada "for basado en rangos", recorrerá cada elemento de la lista. Funcionará con arreglos de estilo C (siempre que se conozca su longitud en tiempo de compilación), listas de inicialización y cualquier tipo que tenga begin()funciones end()definidas que devuelvan iteradores. Todos los contenedores de la biblioteca estándar que tengan pares de inicio/fin funcionarán con la instrucción for basada en rangos.
Funciones y expresiones Lambda
C++11 proporciona la capacidad de crear funciones anónimas , llamadas funciones lambda. [ 14 ] Estas se definen de la siguiente manera:
// Define una función lambda llamada add // Recibe dos enteros y devuelve un entero auto add = []( int x , int y ) -> int { return x + y ; }El tipo de retorno ( en este ejemplo) puede omitirse siempre que todas las expresiones devuelvan el mismo tipo. Una lambda puede ser opcionalmente un cierre .->intreturn
Sintaxis de función alternativa
La sintaxis estándar de declaración de funciones de C era perfectamente adecuada para el conjunto de características del lenguaje C. A medida que C++ evolucionó a partir de C, mantuvo la sintaxis básica y la extendió donde fue necesario. Sin embargo, a medida que C++ se volvió más complejo, expuso varias limitaciones, especialmente en lo que respecta a las declaraciones de funciones plantilla. Por ejemplo, en C++03 esto no es válido:
plantilla < clase L , clase R > Ret add ( const L & lhs , const R & rhs ) { return lhs + rhs ; // Ret debe ser del tipo de lhs + rhs }El tipo es lo que sea que produzca Retla suma de tipos . Incluso con la funcionalidad de C++11 mencionada anteriormente , esto no es posible:LRdecltype
// Plantilla no válida de C++11 < clase L , clase R > decltype ( lhs + rhs ) add ( const L & lhs , const R & rhs ) { return lhs + rhs ; }Esto no es C++ válido porque lhsy rhsaún no se han definido; no serán identificadores válidos hasta que el analizador haya analizado el resto del prototipo de la función.
Para solucionar esto, C++11 introdujo una nueva sintaxis de declaración de funciones, con un tipo de retorno final : [ 15 ]
plantilla < clase L , clase R > auto agregar ( const L & lhs , const R & rhs ) -> decltype ( lhs + rhs ) { return lhs + rhs ; }Esta sintaxis se puede utilizar para declaraciones y definiciones de funciones más comunes:
struct X { static auto add ( int x , int y ) -> int { return x + y ; } };El uso de la autopalabra clave " " en este caso es solo parte de la sintaxis y no realiza deducción automática de tipos en C++11. Sin embargo, a partir de C++14, el tipo de retorno final se puede eliminar por completo y el compilador deducirá el tipo de retorno automáticamente. [ 16 ]
mejora de la construcción de objetos
En C++03, los constructores de una clase no pueden llamar a otros constructores en una lista de inicialización de esa clase. Cada constructor debe construir todos los miembros de su clase por sí mismo o llamar a una función miembro común, como se muestra a continuación:
clase Entero { privado : int valor ;void setValue ( int num ) { valor = num ; } public : Integer ( int number ) { setValue ( number ); }Entero () { setValue ( 42 ); } };Los constructores de las clases base no pueden exponerse directamente a las clases derivadas; cada clase derivada debe implementar sus propios constructores, incluso si un constructor de la clase base fuera apropiado. Los miembros de datos no constantes de las clases no pueden inicializarse en el lugar de su declaración. Solo pueden inicializarse en un constructor.
C++11 ofrece soluciones a todos estos problemas.
C++11 permite que los constructores llamen a otros constructores pares (lo que se denomina delegación ). Esto permite que los constructores utilicen el comportamiento de otro constructor con un mínimo de código adicional. La delegación se ha utilizado en otros lenguajes, como Java y Objective-C .
Esta sintaxis es la siguiente:
clase Entero { privado : int valor ; público : Entero ( int número ) : valor ( número ) {}Entero () : Entero ( 42 ) {} };En este caso, se podría haber logrado el mismo efecto estableciendo numberun parámetro predeterminado. Sin embargo, la nueva sintaxis permite expresar el valor predeterminado (42) en la implementación en lugar de en la interfaz, lo cual resulta beneficioso para quienes mantienen el código de la biblioteca, ya que los valores predeterminados para los parámetros de las funciones están integrados en los puntos de llamada, mientras que la delegación del constructor permite cambiar el valor sin necesidad de recompilar el código que utiliza la biblioteca.
Esto tiene una salvedad: C++03 considera que un objeto está construido cuando su constructor finaliza su ejecución, mientras que C++11 lo considera construido una vez que cualquier constructor finaliza su ejecución. Dado que se permitirá la ejecución de múltiples constructores, esto significa que cada constructor delegador se ejecutará sobre un objeto completamente construido de su propio tipo. Los constructores de las clases derivadas se ejecutarán una vez que se haya completado toda la delegación en sus clases base.
Para los constructores de la clase base, C++11 permite que una clase especifique que los constructores de la clase base se heredarán. Por lo tanto, el compilador de C++11 generará código para realizar la herencia y el reenvío de la clase derivada a la clase base. Esta es una característica de todo o nada: o se reenvían todos los constructores de esa clase base o ninguno. Además, un constructor heredado se ocultará si coincide con la firma de un constructor de la clase derivada, y existen restricciones para la herencia múltiple: los constructores de una clase no pueden heredarse de dos clases que utilicen constructores con la misma firma .
La sintaxis es la siguiente:
clase Base { público : Base ( int valor ); };clase Derivada : pública Base { pública : usando Base :: Base ; };Para la inicialización de miembros, C++11 permite esta sintaxis:
clase Entero { privado : int valor = 5 ; público : Entero () {}Entero explícito ( int valor ) : valor ( valor ) {} };Cualquier constructor de la clase se inicializará valuecon 5, a menos que el constructor sobrescriba la inicialización con la suya propia. Por lo tanto, el constructor vacío anterior se inicializará valuesegún lo especificado en la definición de la clase, pero el constructor que recibe un entero lo inicializará con el valor del parámetro dado.
También puede utilizar la inicialización mediante constructor o uniforme, en lugar de la inicialización por asignación que se muestra arriba.
Anulaciones explícitas y finales
En C++03, es posible crear accidentalmente una nueva función virtual cuando se pretende sobrescribir una función de la clase base. Por ejemplo:
struct Base { virtual void foo ( float x ); };struct Derived : public Base { virtual void foo ( int x ); };Supongamos que Derived::foo()se pretende reemplazar la versión de la clase base. Pero, debido a que tiene una firma diferente , crea una segunda función virtual. Este es un problema común, sobre todo cuando un usuario modifica la clase base.
C++11 proporciona la sintaxis para resolver este problema.
struct Base { virtual void foo ( float x ); };struct Derived : public Base { virtual void foo ( int x ) override ; // mal formado - no sobrescribe un método de la clase base };El overrideidentificador especial significa que el compilador comprobará la(s) clase(s) base para ver si existe una función virtual con esa firma exacta. Si no la hay, el compilador indicará un error.
C++11 también añade la capacidad de impedir la herencia de clases o simplemente impedir la sobrescritura de métodos en clases derivadas. Esto se hace con el identificador especial final. Por ejemplo:
struct Base final { // ... };// mal formado porque la clase Base ha sido marcada como final struct Derived : public Base { // ... };struct Base { virtual void foo () final ; };struct Derived : public Base { void foo (); // mal formado porque la función virtual Base::foo ha sido marcada como final };En este ejemplo, la virtual void foo() final;instrucción declara una nueva función virtual, pero también impide que las clases derivadas la sobrescriban. Además, impide que las clases derivadas utilicen esa combinación específica de nombre de función y parámetros.
Ni overrideni finalson palabras clave del lenguaje. Técnicamente son identificadores de atributos del declarador:
- Adquieren un significado especial como atributos solo cuando se utilizan en esos contextos finales específicos (después de todos los especificadores de tipo, especificadores de acceso, declaraciones de miembros (para tipos struct, class y enum) y especificadores de declaradores, pero antes de la inicialización o implementación de código de cada declarador en una lista de declaradores separados por comas);
- no alteran la firma de tipo declarada y no declaran ni sobrescriben ningún identificador nuevo en ningún ámbito;
- Los atributos de declarador reconocidos y aceptados pueden ampliarse en futuras versiones de C++ (algunas extensiones específicas del compilador ya reconocen atributos de declarador añadidos, para proporcionar opciones de generación de código o sugerencias de optimización al compilador, o para generar datos añadidos en el código compilado, destinados a depuradores, enlazadores y despliegue del código compilado, o para proporcionar atributos de seguridad adicionales específicos del sistema, o para mejorar las capacidades de programación reflexiva (reflexión) en tiempo de ejecución, o para proporcionar información de enlace adicional para la interoperabilidad con otros lenguajes de programación y sistemas de tiempo de ejecución; estas extensiones pueden tomar parámetros entre paréntesis después del identificador del atributo de declarador; para la conformidad con ANSI, estas extensiones específicas del compilador deben usar la convención de prefijo de doble guion bajo).
- En cualquier otro lugar, pueden ser identificadores válidos para nuevas declaraciones (y para su uso posterior si son accesibles).
Constante y tipo de puntero nulo
Para los fines de esta sección y solo de esta sección, cada aparición de " 0" se entiende como "una expresión constante que se evalúa como 0, que es de tipo int". En realidad, la expresión constante puede ser de cualquier tipo entero.
Desde los inicios de C en 1972, la constante 0ha tenido el doble papel de entero constante y puntero nulo constante. La ambigüedad inherente al doble significado de 0se resolvió en C mediante el uso de la macro del preprocesador NULL, que comúnmente se expande a o . C++ prohíbe la conversión implícita de a otros tipos de puntero, eliminando así el beneficio de la conversión a . Como consecuencia, solo se permite como puntero nulo constante. Esto interactúa mal con la sobrecarga de funciones :((void*)0)0void*0void*0
void foo ( char * s ); void foo ( int i );Si NULLse define como 0(que suele ser el caso en C++), la instrucción llamará a , lo cual casi con seguridad no es lo que el programador pretendía, y no es lo que sugiere una lectura superficial del código.foo(NULL);foo(inti)
C++11 corrige esto introduciendo una nueva palabra clave para servir como una constante de puntero nulo distinguido: nullptr. Es de tipo nullptr_t, que es implícitamente convertible y comparable a cualquier tipo de puntero o tipo de puntero a miembro. No es implícitamente convertible ni comparable a tipos enteros, excepto para bool. Si bien la propuesta original especificaba que un rvalue de tipo nullptr_tno debería ser convertible a bool, el grupo de trabajo del lenguaje principal decidió que tal conversión sería deseable, para coherencia con los tipos de puntero regulares. Los cambios de redacción propuestos fueron votados unánimemente en el documento de trabajo en junio de 2008.Una propuesta similar también se presentó al grupo de trabajo del estándar C y fue aceptada para su inclusión en C23 . [ 17 ]
Por razones de compatibilidad con versiones anteriores, 0sigue siendo una constante de puntero nulo válida.
char * pc = nullptr ; // OK int * pi = nullptr ; // OK bool b = nullptr ; // OK. b es falso. int i = nullptr ; // errorfoo ( nullptr ); // llama a foo(nullptr_t p), no a foo(int i); /* Tenga en cuenta que foo(nullptr_t p) en realidad llamará a foo(char* s) en el ejemplo anterior usando una conversión implícita, solo si no hay otras funciones sobrecargadas con tipos de puntero compatibles en el ámbito. Si existen múltiples sobrecargas, la resolución fallará ya que es ambigua, a menos que haya una declaración explícita de foo(nullptr_t p). En los encabezados de tipos estándar para C++11, el tipo nullptr_t debe declararse como: using nullptr_t = decltype(nullptr); pero no como: using nullptr_t = int; // versiones anteriores de C++ que necesitan que NULL se defina como 0 using nullptr_t = void*; // ANSI C que define NULL como ((void*)0) */Enumeraciones fuertemente tipadas
En C++03, las enumeraciones no son seguras en cuanto a tipos. En la práctica, se comportan como enteros, incluso cuando sus tipos son distintos. Esto permite comparar dos valores de enumeración de diferentes tipos. La única seguridad que ofrece C++03 es que un entero o un valor de un tipo de enumeración no se convierte implícitamente a otro tipo de enumeración. Además, el tipo entero subyacente depende de la implementación; por lo tanto, el código que depende del tamaño de la enumeración no es portable. Finalmente, los valores de enumeración tienen un ámbito limitado al ámbito que los contiene. Por consiguiente, no es posible que dos enumeraciones distintas en el mismo ámbito tengan nombres de miembros coincidentes.
C++11 permite una clasificación especial de enumeraciones que no presenta ninguno de estos problemas. Esto se expresa mediante la declaración enum class( enum structtambién se acepta como sinónimo):
clase enumeración CardSuit { Tréboles = 1 , Corazones = 2 , Diamantes = 4 , Picas = 8 };Esta enumeración es segura en cuanto a tipos. Los valores de la clase Enum no se convierten implícitamente a enteros. Por lo tanto, tampoco se pueden comparar con enteros (la expresión produce un error de compilación).CardSuit::SPADES==8
El tipo subyacente de las clases enum siempre se conoce. El tipo predeterminado es int; esto se puede sobrescribir a un tipo entero diferente, como se puede ver en este ejemplo:
clase enumeración Dirección : char { ARRIBA , ABAJO , IZQUIERDA , DERECHA };Con las enumeraciones de estilo antiguo, los valores se ubican en el ámbito externo. Con las enumeraciones de estilo nuevo, se ubican dentro del ámbito del nombre de la clase enum. Por lo tanto, en el ejemplo anterior, UPno está definido, pero sí Direction::UPestá definido.
También existe una sintaxis de transición para permitir que las enumeraciones de estilo antiguo proporcionen un alcance explícito y la definición del tipo subyacente:
enum Dirección : char { ARRIBA , ABAJO , IZQUIERDA , DERECHA };En este caso, los nombres de los enumeradores se definen en el ámbito de la enumeración ( Direction::UP), pero para mantener la compatibilidad con versiones anteriores también se colocan en el ámbito que los contiene.
En C++11 también es posible declarar enumeraciones de forma anticipada. Anteriormente, los tipos de enumeración no podían declararse de esta forma porque el tamaño de la enumeración dependía de la definición de sus miembros. Siempre que el tamaño de la enumeración se especifique, ya sea implícita o explícitamente, puede declararse de forma anticipada.
enum Enum1 ; // No válido en C++03 y C++11; no se puede determinar el tipo subyacente. enum Enum2 : unsigned int ; // Válido en C++11, el tipo subyacente se especifica explícitamente. enum class Enum3 ; // Válido en C++11, el tipo subyacente es int. enum class Enum4 : unsigned int ; // Válido en C++11. enum Enum2 : unsigned short ; // No válido en C++11, porque Enum2 se declaró anteriormente con un tipo subyacente diferente.corchete de ángulo recto
El analizador sintáctico de C++03 define “ ” como el operador de desplazamiento a la derecha o el operador de extracción de flujo en todos los casos. Sin embargo, con las declaraciones de plantillas anidadas, el programador suele olvidar colocar un espacio entre los dos corchetes angulares, lo que provoca un error de sintaxis del compilador.>>
C++11 mejora la especificación del analizador sintáctico de modo que los corchetes angulares múltiples se interpretarán como el cierre de la lista de argumentos de la plantilla cuando sea apropiado. Esto se puede anular utilizando paréntesis alrededor de las expresiones de parámetros mediante los operadores binarios “ >”, “ >=” o “ ”:>>
usando std :: vector ;template < bool Test > class X ; vector < X < 1 > 2 >> x1 ; // Interpretado como un std::vector de X<true>, // seguido de "2 >> x1", que no es una sintaxis válida para un declarador. 1 es verdadero.vector < X < ( 1 > 2 ) >> x1 ; // Interpretado como std::vector de X<false>, // seguido del declarador "x1", que es sintaxis válida de C++11. (1>2) es falso.Operadores de conversión explícitos
C++98 añadió la explicitpalabra clave como modificador en los constructores para evitar que los constructores de un solo argumento se utilicen como operadores de conversión de tipo implícitos. Sin embargo, esto no afecta a los operadores de conversión reales. Por ejemplo, una clase de puntero inteligente puede tener un para permitirle comportarse más como un puntero primitivo: si incluye esta conversión, se puede comprobar con (que sería verdadero si el puntero no fuera nulo y falso en caso contrario). Sin embargo, esto también permite otras conversiones no deseadas. Dado que C++ se define como un tipo aritmético, puede convertirse implícitamente a tipos enteros o incluso de punto flotante, lo que permite operaciones matemáticas no previstas por el usuario.operatorbool()if(p)bool
En C++11, la explicitpalabra clave ahora se puede aplicar a los operadores de conversión. Al igual que con los constructores, impide el uso de esas funciones de conversión en conversiones implícitas. Sin embargo, los contextos del lenguaje que requieren específicamente un valor booleano (las condiciones de las sentencias if y los bucles, y los operandos de los operadores lógicos) se consideran conversiones explícitas y, por lo tanto, pueden usar un operador de conversión booleana.
Por ejemplo, esta característica resuelve de forma limpia el problema de los valores booleanos seguros .
Alias de plantilla
En C++03, solo es posible definir un typedef como sinónimo de otro tipo, incluyendo un sinónimo para una especialización de plantilla con todos los argumentos de plantilla reales especificados. No es posible crear una plantilla typedef. Por ejemplo:
plantilla < typename First , typename Second , int Third > clase X ;plantilla < typename Second > typedef X < A , Second , 5 > Y ; // Inválido en C++03Esto no compilará.
C++11 añade esta capacidad con esta sintaxis:
plantilla < typename First , typename Second , int Third > clase X ;plantilla < typename Second > usando Y = X < A , Second , 5 > ;La usingsintaxis también se puede utilizar como alias de tipo en C++11:
typedef void ( * FunctionType )( double ); // Estilo antiguo using FunctionType = void ( * )( double ); // Nueva sintaxis introducidasindicatos sin restricciones
En C++03, existen restricciones sobre qué tipos de objetos pueden ser miembros de una unión union. Por ejemplo, las uniones no pueden contener objetos que definan un constructor o destructor no trivial. C++11 elimina algunas de estas restricciones.
Si un unionmiembro tiene una función miembro especial no trivial , el compilador no generará la función miembro equivalente uniony deberá definirse manualmente.
Este es un ejemplo sencillo de una unión permitida en C++11:
#include <new> // Necesario para la colocación de 'new'.struct Punto { int a ; int b ;Punto () {} Punto ( int x , int y ) : x ( a ), y ( b ) {} };unión MyUnion { int z ; double w ; Point p ; // Inválido en C++03; válido en C++11.// Debido al miembro Point, ahora se necesita una definición de constructor. MyUnion () {}MyUnion ( const Point & pt ) : p ( pt ) {} // Construye un objeto Point usando la lista de inicialización.MyUnion & operator = ( const Point & pt ) { // Asigna el objeto Point usando la ubicación 'new'. new ( & p ) Point ( pt ); return * this ; } };Estos cambios no afectarán al código existente, ya que solo flexibilizan las reglas actuales.
Mejoras en la funcionalidad del lenguaje principal
Estas características permiten que el lenguaje haga cosas que antes eran imposibles, excesivamente prolijas o que requerían bibliotecas no portátiles.
Plantillas variádicas
En C++11, las plantillas pueden aceptar un número variable de parámetros. Esto también permite la definición de funciones variádicas con seguridad de tipos .
Nuevos literales de cadena
C++03 ofrece dos tipos de literales de cadena . El primer tipo, contenido entre comillas dobles, produce una matriz terminada en nulo de tipo const char. El segundo tipo, definido como L"", produce una matriz terminada en nulo de tipo const wchar_t, donde wchar_tes un carácter ancho de tamaño y semántica indefinidos. Ninguno de los tipos de literales ofrece soporte para literales de cadena con UTF-8 , UTF-16 o cualquier otro tipo de codificación Unicode .
C++11 admite tres codificaciones Unicode: UTF-8, UTF-16 y UTF-32 . La definición del tipo charse ha modificado para expresar explícitamente que tiene al menos el tamaño necesario para almacenar una codificación de ocho bits de UTF-8, y es lo suficientemente grande como para contener cualquier miembro del conjunto básico de caracteres de ejecución del compilador. Anteriormente, en el propio estándar de C++, solo se definía como esto último, y luego se basaba en el estándar de C para garantizar al menos 8 bits. Además, C++11 añade dos nuevos tipos de caracteres: char16_ty char32_t. Estos están diseñados para almacenar UTF-16 y UTF-32 respectivamente.
La creación de literales de cadena para cada una de las codificaciones admitidas se puede realizar de la siguiente manera:
u8 "Soy una cadena UTF-8." u "Esta es una cadena UTF-16." U "Esta es una cadena UTF-32."El tipo de la primera cadena es el habitual const char[]. El tipo de la segunda cadena es const char16_t[](nótese el prefijo 'u' minúscula). El tipo de la tercera cadena es const char32_t[](prefijo 'U' mayúscula).
Al construir literales de cadena Unicode, a menudo resulta útil insertar puntos de código Unicode directamente en la cadena. Para ello, C++11 permite esta sintaxis:
u8 "Este es un carácter Unicode: \u2018 ." u "Este es un carácter Unicode más grande: \u2018 ." U "Este es un carácter Unicode: \U00002018 ."El número que sigue \ues un número hexadecimal; no necesita el 0xprefijo habitual. El identificador \urepresenta un punto de código Unicode de 16 bits; para introducir un punto de código de 32 bits, utilice \Uy un número hexadecimal de 32 bits. Solo se pueden introducir puntos de código Unicode válidos. Por ejemplo, los puntos de código en el rango U+D800–U+DFFF están prohibidos, ya que están reservados para pares subrogados en codificaciones UTF-16.
También resulta útil en ocasiones evitar el escape manual de cadenas, especialmente al utilizar literales de archivos XML , lenguajes de scripting o expresiones regulares. C++11 proporciona un literal de cadena sin formato:
R " ( Los datos de cadena \ cosas " ) " R " delimitador( Los datos de cadena \ cosas " )delimitador "En el primer caso, todo lo que se encuentra entre "(y )"forma parte de la cadena. Los caracteres "y \no necesitan ser escapados. En el segundo caso, "delimiter(inicia la cadena y solo termina cuando )delimiter"se alcanza. La cadena delimiterpuede ser cualquier cadena de hasta 16 caracteres de longitud, incluyendo la cadena vacía. Esta cadena no puede contener espacios, caracteres de control, , (, )ni el \carácter . Usando esta cadena delimitadora, el usuario puede tener la secuencia )"dentro de literales de cadena sin formato. Por ejemplo, es equivalente a .R"delimiter("(a-z)")delimiter""\"(a-z)\""
Las cadenas literales sin formato se pueden combinar con el literal ancho o con cualquiera de los prefijos literales Unicode:
u8R " XXX( Soy una cadena "UTF-8 sin procesar". )XXX " uR " *( Esta es una cadena "UTF-16 sin procesar". )* " UR " ( Esta es una cadena "UTF-32 sin procesar". ) "Literales definidos por el usuario
C++03 proporciona varios literales. Los caracteres 12.5son un literal que el compilador resuelve como un tipo doublecon el valor 12.5. Sin embargo, la adición del sufijo f, como en 12.5f, crea un valor de tipo floatque contiene el valor 12.5. Los modificadores de sufijo para literales están fijos en la especificación de C++, y el código C++03 no puede crear nuevos modificadores de literales.
Por el contrario, C++11 permite al usuario definir nuevos tipos de modificadores literales que construirán objetos basándose en la cadena de caracteres que modifica el literal.
La transformación de literales se redefine en dos fases distintas: cruda y cocinada. Un literal crudo es una secuencia de caracteres de un tipo específico, mientras que el literal cocinado es de un tipo diferente. El literal de C++ 1234, como literal crudo, es esta secuencia de caracteres '1', '2', '3', '4'. Como literal cocinado, es el entero 1234. El literal de C++ 0xAen forma cruda es '0', 'x', 'A', mientras que en forma cocinada es el entero 10.
Los literales pueden extenderse tanto en su forma original como procesada, con la excepción de los literales de cadena, que solo pueden procesarse en su forma procesada. Esta excepción se debe a que las cadenas tienen prefijos que afectan el significado y el tipo específicos de los caracteres en cuestión.
Todos los literales definidos por el usuario son sufijos; no es posible definir literales de prefijo. Todos los sufijos que comienzan con cualquier carácter excepto el guion bajo ( _) están reservados por el estándar. Por lo tanto, todos los literales definidos por el usuario deben tener sufijos que comiencen con un guion bajo ( _). [ 18 ]
Los literales definidos por el usuario que procesan la forma original del literal se definen mediante un operador literal, que se escribe como operator"". A continuación se muestra un ejemplo:
operador MyOutput "" _suf ( const char * s ) { // asume que MyOutput tiene un constructor que acepta un const char* MyOutput ret ( s ); return ret ; }MyOutput x = 1234 _suf ; // suponiendo que MyOutput tiene un método value() que devuelve un double assert ( x . value () == 1234.0 )La instrucción de asignación ejecuta el código definido por la función literal definida por el usuario. Esta función se pasa como una cadena de estilo C, por lo que tiene un terminador nulo.MyOutputx=1234_suf;"1234"
Un mecanismo alternativo para procesar literales sin procesar enteros y de punto flotante es mediante una plantilla variádica :
plantilla < char ... _ > MyOutput operador "" _suf ();MiSalida x = 1234 _suf ; MiSalida y = 2.17 _suf ;Esto instancia la función de procesamiento literal como . En esta forma, no hay un carácter nulo que termine la cadena. El propósito principal de hacer esto es usar la palabra clave de C++11 para asegurar que el compilador transformará el literal completamente en tiempo de compilación, suponiendo que es un tipo construible y copiable con constexpr, y que la función de procesamiento literal es una función.operator""_suf<'1','2','3','4'>()constexprMyOutputconstexpr
Para literales numéricos, el tipo del literal procesado es unsigned long longpara literales enteros o long doublepara literales de punto flotante. (Nota: No se necesitan tipos enteros con signo porque un literal con prefijo de signo se analiza como una expresión que contiene el signo como operador de prefijo unario y el número sin signo). No existe una plantilla alternativa:
Operador MyOutput "" _suf ( unsigned long long ); Operador MyOutput "" _suf ( long double );MyOutput x = 1234 _suf ; // Utiliza la sobrecarga 'unsigned long long'. MyOutput y = 3.1416 _suf ; // Utiliza la sobrecarga 'long double'.De acuerdo con los nuevos prefijos de cadena mencionados anteriormente, para los literales de cadena se utilizan los siguientes:
MyOutput operator "" _suf ( const char * s , size_t length ); MyOutput operator "" _suf ( const wchar_t * s , size_t length ); MyOutput operator "" _suf ( const char16_t * s , size_t length ); MyOutput operator "" _suf ( const char32_t * s , size_t length );MyOutput a = "1234" _suf ; // Usa la sobrecarga 'const char*'. MyOutput b = u8 "1234" _suf ; // Usa la sobrecarga 'const char*'. MyOutput c = L "1234" _suf ; // Usa la sobrecarga 'const wchar_t*'. MyOutput d = u "1234" _suf ; // Usa la sobrecarga 'const char16_t*'. MyOutput e = U "1234" _suf ; // Usa la sobrecarga 'const char32_t*'.No existe una plantilla alternativa. Los caracteres literales se definen de forma similar.
Modelo de memoria multihilo
C++11 estandariza la compatibilidad con la programación multihilo .
Hay dos partes involucradas: un modelo de memoria que permite la coexistencia de múltiples hilos en un programa y soporte de biblioteca para la interacción entre hilos. (Consulte la sección de este artículo sobre funcionalidades de subprocesos ).
El modelo de memoria define cuándo varios hilos pueden acceder a la misma ubicación de memoria y especifica cuándo las actualizaciones realizadas por un hilo se vuelven visibles para otros hilos.
Almacenamiento local de subprocesos
En un entorno multihilo, es común que cada hilo tenga algunas variables únicas . Esto ya ocurre con las variables locales de una función, pero no con las variables globales y estáticas.
El especificador de almacenamiento indica una nueva duración de almacenamiento local para cada hilo (además de las opciones estática , dinámica y automáticathread_local ya existentes) .
Cualquier objeto que pueda tener una duración de almacenamiento estática (es decir, una vida útil que abarque toda la ejecución del programa) puede tener una duración local para cada hilo. La idea es que, al igual que cualquier otra variable de duración estática, un objeto local para cada hilo pueda inicializarse mediante un constructor y destruirse mediante un destructor.
Funciones miembro especiales con valores predeterminados explícitos
En C++03, el compilador proporciona, para las clases que no los proporcionan por sí mismas, un constructor predeterminado, un constructor de copia, un operador de asignación de copia ( operator=), y un destructor. El programador puede sobrescribir estos valores predeterminados definiendo versiones personalizadas. C++ también define varios operadores globales (como operator new) que funcionan en todas las clases, los cuales el programador puede sobrescribir.
Sin embargo, existe muy poco control sobre la creación de estos valores predeterminados. Por ejemplo, para que una clase sea inherentemente no copiable, se puede declarar un constructor de copia privado y un operador de asignación de copia, pero sin definirlos. Intentar usar estas funciones constituye una violación de la Regla de una Definición (ODR). Si bien no se requiere un mensaje de diagnóstico, [ 19 ] las violaciones pueden generar un error del enlazador.
En el caso del constructor predeterminado, el compilador no generará uno si una clase ya está definida con constructores . Esto resulta útil en muchos casos, pero también es conveniente poder disponer tanto de constructores especializados como del constructor predeterminado generado por el compilador.
C++11 permite la asignación de valores predeterminados y la eliminación explícita de estas funciones miembro especiales. [ 20 ] Por ejemplo, esta clase declara explícitamente que se puede utilizar un constructor predeterminado:
clase X { X () = default ; // El constructor predeterminado se indica explícitamente. X ( T valor ); };Funciones eliminadas explícitamente
Una función puede deshabilitarse explícitamente. Esto es útil para evitar conversiones de tipo implícitas. El = deleteespecificador puede usarse para prohibir la llamada a una función con tipos de parámetros particulares. [ 20 ] Por ejemplo:
void noInt ( double i ); void noInt ( int ) = delete ;Un intento de llamar noInt()con un intparámetro será rechazado por el compilador, en lugar de realizar una conversión silenciosa a double. Llamar noInt()con un floattodavía funciona.
Es posible prohibir la llamada a la función con cualquier otro tipo que no sea doublemediante el uso de una plantilla:
double onlyDouble ( double d ) { return d ; }plantilla < typename T > double onlyDouble ( T ) = eliminar ;La llamada onlyDouble(1.0)funcionará, mientras que onlyDouble(1.0f)generará un error de compilación.
También se pueden eliminar las funciones miembro y los constructores de la clase. Por ejemplo, es posible evitar la copia de objetos de clase eliminando el constructor de copia y operator =:
clase NonCopyable { NonCopyable (); NonCopyable ( const NonCopyable & ) = delete ; NonCopyable & operator = ( const NonCopyable & ) = delete ; };Tipolong long int
En C++03, el tipo entero más grande es long int. Se garantiza que tiene al menos tantos bits utilizables como int. Esto resultó en long intun tamaño de 64 bits en algunas implementaciones populares y de 32 bits en otras. C++11 agrega un nuevo tipo entero long long intpara abordar este problema. Se garantiza que es al menos tan grande como un long int, y que tiene no menos de 64 bits. El tipo fue introducido originalmente por C99 en el estándar C, y la mayoría de los compiladores de C++ ya lo soportaban como una extensión. [ 21 ] [ 22 ]
Aserciones estáticas
C++03 proporciona dos métodos para probar aserciones : la macro asserty la directiva del preprocesador #error. Sin embargo, ninguno es apropiado para usar en plantillas: la macro prueba la aserción en tiempo de ejecución, mientras que la directiva del preprocesador la prueba durante el preprocesamiento, que ocurre antes de la instanciación de las plantillas. Ninguno es apropiado para probar propiedades que dependen de parámetros de plantilla.
La nueva utilidad introduce una nueva forma de probar aserciones en tiempo de compilación, utilizando la nueva palabra clave static_assert. La declaración adopta esta forma:
static_assert( expresión-constante , mensaje-de-error );
Aquí tienes algunos ejemplos de cómo static_assertse puede utilizar:
static_assert (( GREEK_PI > 3.14 ) && ( GREEK_PI < 3.15 ), "¡GREEK_PI es inexacto!" );plantilla < clase T > struct Check { static_assert ( sizeof ( int ) <= sizeof ( T ), "¡T no es lo suficientemente grande!" ); };usando std :: is_integral ;template < class Int > Int foo ( Int x , Int y ) { static_assert ( is_integral < Int >:: value , "El parámetro foo() debe ser de tipo entero." ); }Cuando la expresión constante es falseel compilador produce un mensaje de error. El primer ejemplo es similar a la directiva del preprocesador #error, aunque el preprocesador solo admite tipos enteros. [ 23 ] En contraste, en el segundo ejemplo la aserción se verifica en cada instanciación de la clase plantilla Check.
Las aserciones estáticas también son útiles fuera de las plantillas. Por ejemplo, una implementación determinada de un algoritmo podría depender de que el tamaño de long longsea mayor que el de int, algo que el estándar no garantiza. Esta suposición es válida en la mayoría de los sistemas y compiladores, pero no en todos.
Permite sizeoftrabajar con miembros de clases sin un objeto explícito.
En C++03, el sizeofoperador se puede usar en tipos y objetos. Pero no se puede usar para hacer esto:
struct X { T miembro ; };sizeof ( X :: member ); // No funciona con C++03. Funciona con C++11.Esto debería devolver el tamaño de T. C++03 no lo permite, por lo que es un error de compilación. C++11 lo permite. También está permitido para el alignofoperador introducido en C++11.
Alineación de objetos de control y consulta
C++11 permite consultar y controlar la alineación de variables con alignofy alignas.
El alignofoperador toma el tipo y devuelve el límite de bytes (potencia de 2) en el que deben asignarse las instancias del tipo (como un std::size_t). Cuando se le proporciona un tipo de referencia, alignofdevuelve la alineación del tipo referenciado; para matrices, devuelve la alineación del tipo de elemento.
El alignasespecificador controla la alineación de memoria para una variable. El especificador acepta una constante o un tipo; cuando se proporciona un tipo, es una abreviatura de . Por ejemplo, para especificar que una variable debe estar correctamente alineada para contener un valor :alignas(T)alignas(alignof(T))char[]float
alinea ( float ) unsigned char c [ sizeof ( float )];Permitir implementaciones con recolección de basura
Las normas anteriores de C++ contemplaban la recolección de basura controlada por el programador, pero no definían la accesibilidad de los objetos para la recolección automática. C++11 define las condiciones bajo las cuales los valores de los punteros se derivan de forma segura de otros valores. Una implementación puede especificar que opera bajo una estricta seguridad de punteros , en cuyo caso los punteros que no se derivan según estas reglas pueden volverse inválidos. Esto se eliminó posteriormente en C++23 . [ 24 ]
Atributos
C++11 proporciona una sintaxis estandarizada para las extensiones de compiladores y herramientas del lenguaje. Tradicionalmente, dichas extensiones se especificaban mediante #pragmadirectivas o palabras clave específicas del proveedor (como __attribute__en GNU y __declspecMicrosoft). Con la nueva sintaxis, la información adicional se puede especificar en forma de atributo entre corchetes dobles. Un atributo se puede aplicar a diversos elementos del código fuente:
int [[ attr1 ]] i [[ attr2 , attr3 ]];[[ attr4 ( arg1 , arg2 )]] if ( cond ) { [[ vendor :: attr5 ]] return i ; }En el ejemplo anterior, el atributo se aplica al tipo de variable , y se aplica a la variable misma, se aplica a la instrucción y se aplica a la instrucción de retorno. En general (pero con algunas excepciones), un atributo especificado para una entidad con nombre se coloca después del nombre, y antes de la entidad en caso contrario, como se muestra arriba, varios atributos pueden enumerarse dentro de un par de corchetes dobles, se pueden proporcionar argumentos adicionales para un atributo, y los atributos pueden estar delimitados por espacios de nombres de atributos específicos del proveedor.[[attr1]]i[[attr2]][[attr3]][[attr4]]if[[vendor::attr5]]
Se recomienda que los atributos no tengan significado semántico en el lenguaje y que, al ignorarlos, no alteren el sentido del programa. Los atributos pueden ser útiles para proporcionar información que, por ejemplo, ayude al compilador a generar mejores diagnósticos u optimizar el código generado.
C++11 proporciona dos atributos estándar: uno para especificar que una función no devuelve ningún valor y otro para ayudar a optimizar el código multihilo indicando que los argumentos de la función o el valor de retorno conllevan una dependencia.[[noreturn]][[carries_dependency]]
Cambios en la biblioteca estándar de C++
En la biblioteca estándar de C++11 se introdujeron varias características nuevas. Muchas de ellas podrían haberse implementado con el estándar anterior, pero algunas dependen (en mayor o menor medida) de las nuevas características principales de C++11.
Gran parte de las nuevas bibliotecas se definieron en el documento Informe Técnico de la Biblioteca del Comité de Estándares de C++ (denominado TR1), publicado en 2005. Actualmente, existen varias implementaciones completas y parciales de TR1 disponibles en el espacio de nombres std::tr1. Para C++11, se trasladaron al espacio de nombres std. Sin embargo, a medida que las características de TR1 se incorporaron a la biblioteca estándar de C++11, se actualizaron cuando fue necesario con características del lenguaje C++11 que no estaban disponibles en la versión inicial de TR1. Además, es posible que se hayan mejorado con características que eran posibles en C++03, pero que no formaban parte de la especificación original de TR1.
Actualizaciones de los componentes de la biblioteca estándar
C++11 ofrece una serie de nuevas características de lenguaje de las que pueden beneficiarse los componentes de la biblioteca estándar existentes. Por ejemplo, la mayoría de los contenedores de la biblioteca estándar pueden beneficiarse de la compatibilidad con constructores de movimiento basados en referencias Rvalue, tanto para mover rápidamente contenedores pesados como para mover el contenido de dichos contenedores a nuevas ubicaciones de memoria. Los componentes de la biblioteca estándar se actualizaron con las nuevas características de lenguaje de C++11 cuando fue apropiado. Estas incluyen, entre otras:
- Referencias de valores R y soporte de movimiento asociado
- Compatibilidad con los tipos de caracteres Unicode de la unidad de codificación UTF-16 y la unidad de codificación UTF-32.
- Plantillas variádicas (junto con referencias a valores R para permitir un reenvío perfecto)
- Expresiones constantes en tiempo de compilación
decltypeexplicitoperadores de conversión- Funciones declaradas con valor predeterminado o eliminadas
Además, ha transcurrido mucho tiempo desde la publicación del estándar C++ anterior. Se ha escrito mucho código que utiliza la biblioteca estándar, lo que ha permitido identificar áreas de mejora en dicha biblioteca. Entre las numerosas áreas de mejora consideradas se encontraban los asignadores de memoria de la biblioteca estándar . En C++11 se incluyó un nuevo modelo de asignadores basado en ámbitos para complementar el modelo anterior.
Instalaciones de enhebrado
Si bien el lenguaje C++03 proporciona un modelo de memoria que admite subprocesos, la principal compatibilidad para el uso real de subprocesos proviene de la biblioteca estándar de C++11.
std::threadSe proporciona una clase de hilo ( ), que acepta un objeto de función (y una serie opcional de argumentos para pasarle) para ejecutarse en el nuevo hilo. Es posible detener un hilo hasta que otro hilo en ejecución finalice, proporcionando soporte para la unión de hilos mediante la std::thread::join()función miembro. Se proporciona acceso, cuando sea factible, al/los objeto/s de hilo nativo/s subyacente/s para operaciones específicas de la plataformastd::thread::native_handle() mediante la función miembro.
Para la sincronización entre hilos, se agregan a la biblioteca los mutex apropiados ( std::mutex, std::recursive_mutex, etc.) y las variables de condición ( std::condition_variabley ). Estos son accesibles a través de bloqueos RAII ( Resource Acquisition Is Initialization ) ( y ) y algoritmos de bloqueo para facilitar su uso.std::condition_variable_anystd::lock_guardstd::unique_lock
Para tareas de bajo nivel y alto rendimiento, a veces es necesario comunicarse entre hilos sin la sobrecarga de los mutex. Esto se logra mediante operaciones atómicas en ubicaciones de memoria. Estas operaciones pueden especificar opcionalmente las restricciones mínimas de visibilidad de memoria necesarias para una operación. También se pueden utilizar barreras de memoria explícitas para este fin.
La biblioteca de hilos de C++11 también incluye futuros y promesas para pasar resultados asíncronos entre hilos, y std::packaged_taskpara encapsular una llamada a función que puede generar dicho resultado asíncrono. La propuesta de futuros fue criticada porque carece de una forma de combinar futuros y comprobar la finalización de una promesa dentro de un conjunto de promesas. [ 25 ]
Otras funcionalidades avanzadas de subprocesos, como los grupos de subprocesos, se han pospuesto para un futuro informe técnico de C++ . No forman parte de C++11, pero se espera que su implementación final se base completamente en las características de la biblioteca de subprocesos.
La nueva std::asyncfuncionalidad proporciona un método práctico para ejecutar tareas y vincularlas a un grupo de subprocesos std::future. El usuario puede elegir si la tarea se ejecutará de forma asíncrona en un subproceso independiente o de forma síncrona en un subproceso que espera el valor. Por defecto, la implementación puede elegir la opción de espera, lo que facilita el aprovechamiento de la concurrencia del hardware sin sobrecargar el sistema y ofrece algunas de las ventajas de un grupo de subprocesos para usos sencillos.
Tipos de tuplas
Las tuplas son colecciones compuestas por objetos heterogéneos de dimensiones predefinidas. Una tupla puede considerarse una generalización de las variables miembro de una estructura.
La versión de C++11 del tipo de tupla TR1 se benefició de características de C++11 como las plantillas variádicas . Para una implementación razonable, la versión TR1 requería un número máximo de tipos contenidos definido por la implementación y un uso considerable de macros. Por el contrario, la implementación de la versión de C++11 no requiere un número máximo de tipos definido explícitamente. Si bien los compiladores tendrán una profundidad máxima de recursión interna para la instanciación de plantillas (lo cual es normal), la versión de C++11 de las tuplas no expondrá este valor al usuario.
Utilizando plantillas variádicas , la declaración de la clase tupla se ve así:
espacio de nombres std { plantilla < clase ... Ts > clase tupla ; }Un ejemplo de definición y uso del tipo tupla:
using Types = std :: tuple < int , double , long & , const char *> ; long lengthy = 12 ; Types proof ( 18 , 6.5 , lengthy , "¡Ciao!" );lengthy = std :: get <0> ( proof ); // Asigna a 'lengthy' el valor 18. std :: get <3> ( proof ) = "Beautiful!" ; // Modifica el cuarto elemento de la tupla .Es posible crear una tupla proofsin definir su contenido, pero solo si los tipos de los elementos de la tupla poseen constructores predeterminados. Además, es posible asignar una tupla a otra: si los tipos de ambas tuplas son iguales, cada tipo de elemento debe tener un constructor de copia; de lo contrario, cada tipo de elemento de la tupla derecha debe ser convertible al tipo de elemento correspondiente de la tupla izquierda, o bien, el tipo de elemento correspondiente de la tupla izquierda debe tener un constructor adecuado.
using std :: string ; using T1 = std :: tuple < int , double , string > ; using T2 = std :: tuple < char , short , const char *> ;T1 t1 ; T2 t2 ( 'X' , 2 , "¡Hola!" ); t1 = t2 ; // Bien, los dos primeros elementos se pueden convertir, // el tercero se puede construir a partir de un 'const char *'.Al igual que std::make_pairpara std::pair, existe std::make_tuplepara crear automáticamente std::tuples usando deducción de tipos y autoayuda a declarar dicha tupla. std::tiecrea tuplas de referencias lvalue para ayudar a desempaquetar tuplas. std::ignoretambién ayuda aquí. Vea el ejemplo:
#include <cadena> #include <tupla>usando std :: string ;auto record = std :: make_tuple ( "Hari Ram" , "Nueva Delhi" , 3.5 , 'A' ); string name ; float gpa ; char grade ; std :: tie ( name , std :: ignore , gpa , grade ) = record ; // std::ignore ayuda a eliminar el nombre del lugar std :: cout << name << ' ' << gpa << ' ' << grade << std :: endl ;Se dispone de operadores relacionales (entre tuplas con el mismo número de elementos) y de dos expresiones para comprobar las características de una tupla (solo durante la compilación):
std::tuple_size<T>::valuedevuelve el número de elementos en la tuplaT,std::tuple_element<I, T>::typedevuelve el tipo del número de objetoIde la tuplaT.
Tablas hash
Una de las peticiones más frecuentes es la inclusión de tablas hash (contenedores asociativos no ordenados) en la biblioteca estándar de C++. No se adoptó en C++03 únicamente por limitaciones de tiempo. Si bien las tablas hash son menos eficientes que un árbol equilibrado en el peor de los casos (ante múltiples colisiones), ofrecen un mejor rendimiento en muchas aplicaciones reales.
Las colisiones se gestionan únicamente mediante encadenamiento lineal, ya que el comité no consideró oportuno estandarizar soluciones de direccionamiento abierto que introducen numerosos problemas intrínsecos (sobre todo cuando se admite el borrado de elementos). Para evitar conflictos de nombres con bibliotecas no estándar que desarrollaron sus propias implementaciones de tablas hash, se utilizó el prefijo «unordered» en lugar de «hash».
La nueva biblioteca cuenta con cuatro tipos de tablas hash, que se diferencian por si aceptan o no elementos con la misma clave (claves únicas o equivalentes) y por si asignan a cada clave un valor asociado. Estas tablas corresponden a los cuatro contenedores asociativos existentes basados en árboles de búsqueda binaria , con el prefijo `unordered_` .
Las nuevas clases cumplen con todos los requisitos de una clase contenedora y tienen todos los métodos necesarios para acceder a los elementos: insert, erase, begin, end.
Esta nueva característica no requirió extensiones del núcleo del lenguaje C++ (aunque las implementaciones aprovecharán diversas características del lenguaje C++11), solo una pequeña extensión del archivo de cabecera <functional>y la introducción de los archivos de cabecera <unordered_set>y <unordered_map>. No se requirieron otros cambios en las clases estándar existentes, y no depende de ninguna otra extensión de la biblioteca estándar.
std::array y std::forward_list
Además de las tablas hash, se añadieron dos contenedores más a la biblioteca estándar. El primero std::arrayes un contenedor de tamaño fijo que es más eficiente que el segundo std::vector, pero más seguro y fácil de usar que un array de estilo C. El std::forward_listtercero es una lista enlazada simple que proporciona un almacenamiento más eficiente en cuanto a espacio que la lista enlazada doble std::listcuando no se necesita iteración bidireccional.
expresiones regulares
La nueva biblioteca, definida en el nuevo archivo de cabecera <regex>, está compuesta por un par de clases nuevas:
- Las expresiones regulares están representadas por una instancia de la clase plantilla
std::regex; - Las ocurrencias están representadas por una instancia de la clase plantilla
std::match_results, std::regex_iteratorse utiliza para iterar sobre todas las coincidencias de una expresión regular (constd::cregex_iteratorparaconst char*ystd::sregex_iteratorparastd::string)
La función std::regex_searchse utiliza para buscar, mientras que para 'buscar y reemplazar' std::regex_replacese utiliza la función que devuelve una nueva cadena. [ 26 ]
Aquí hay un ejemplo del uso de std::regex_iterator:
#incluir <regex>using std :: regex ; using std :: sregex_iterator ; using std :: string ;string pattern = R " ( [^ ,.\t\n]+ ) " ; // encuentra palabras separadas por espacio, coma, punto tabulación salto de línea string target = "Universidad Invisible - Ankh-Morpork" ;regex re ( patrón ); // lanza una excepción si el patrón no es válido// Usa un iterador de expresiones regulares para identificar todas las palabras de 'objetivo' separadas por caracteres de 'patrón'. for ( sregex_iterator it ( target . begin (), target . end (), re ); it != re . end (); ++ it ) { string s = it -> str (); std :: cout << s << std :: endl ; }La biblioteca <regex>no requiere ninguna modificación de ningún archivo de cabecera existente (aunque los utilizará cuando corresponda) ni una extensión del lenguaje principal. En POSIX C, las expresiones regulares también están disponibles a través de <regex.h> .
Punteros inteligentes de uso general
C++11 proporciona std::unique_ptrmejoras con std::shared_ptrrespecto a std::weak_ptrTR1. std::auto_ptrEstá obsoleto.
Instalación de números aleatorios extensible
La biblioteca estándar de C permite generar números pseudoaleatorios mediante la función `switched` rand. Sin embargo, el algoritmo se delega completamente al proveedor de la biblioteca. C++ heredó esta funcionalidad sin cambios, pero C++11 proporciona un nuevo método para generar números pseudoaleatorios.
La funcionalidad de generación de números aleatorios de C++11 se divide en dos partes: un motor generador que contiene el estado del generador de números aleatorios y produce los números pseudoaleatorios; y una distribución, que determina el rango y la distribución matemática del resultado. Estas dos partes se combinan para formar un objeto generador de números aleatorios.
A diferencia del estándar C rand, el mecanismo de C++11 vendrá con tres algoritmos de motor de generación base:
C++11 también proporciona una serie de distribuciones estándar:
uniform_int_distribution,uniform_real_distribution,bernoulli_distribution,binomial_distribution,geometric_distribution,negative_binomial_distribution,poisson_distribution,exponential_distribution,gamma_distribution,weibull_distribution,extreme_value_distribution,normal_distribution,lognormal_distribution,chi_squared_distribution,cauchy_distribution,fisher_f_distribution,student_t_distribution,discrete_distribution,piecewise_constant_distributionypiecewise_linear_distribution.
El generador y las distribuciones se combinan como en este ejemplo:
#include <functional> #include <random>using std :: mt19937 ; using std :: uniform_int_distribution ;uniform_int_distribution < int > distribution ( 0 , 99 ); mt19937 engine ; // Mersenne twister MT19937 auto generator = std :: bind ( distribution , engine ); int random = generator (); // Genera una variable entera uniforme entre 0 y 99. int random2 = distribution ( engine ); // Genera otra muestra directamente usando los objetos distribution y engine.Referencia del envoltorio
Una referencia de envoltura se obtiene de una instancia de la plantilla de clase reference_wrapper. Las referencias de envoltura son similares a las referencias normales (' &') del lenguaje C++. Para obtener una referencia de envoltura de cualquier objeto refse utiliza la plantilla de función (para una referencia constante crefse utiliza ).
Las referencias de envoltura son útiles sobre todo para las plantillas de funciones, donde se necesitan referencias a parámetros en lugar de copias:
#include <functional> #include <iostream>// Esta función tomará una referencia al parámetro 'r' y la incrementará. void func ( int & r ) { r ++ ; }// Función de plantilla. template < class F , class P > void g ( F f , P t ) { f ( t ); }int main () { int i = 0 ; g ( func , i ); // 'g<void(int& r), int>' se instancia // entonces 'i' no se modificará.std :: cout << i << std :: endl ; // Salida -> 0g ( func , std :: ref ( i )); // Se instancia 'g<void(int& r), reference_wrapper<int>>' // luego se modificará 'i'. std :: cout << i << std :: endl ; // Salida -> 1 }Esta nueva utilidad se añadió al <functional>archivo de cabecera existente y no necesitó extensiones adicionales del lenguaje C++.
Envoltorios polimórficos para objetos de función
Los envoltorios polimórficos para objetos de función son similares a los punteros a funciones en semántica y sintaxis, pero están menos restringidos y pueden referirse indiscriminadamente a cualquier cosa que pueda ser llamada (punteros a funciones, punteros a funciones miembro o functores) cuyos argumentos sean compatibles con los del envoltorio.
Un ejemplo puede aclarar sus características:
#include <functional>usando std :: function ; usando std :: plus ;// Creación de envoltorio usando la clase plantilla 'function'. function < int ( int , int ) > func ;plus < int > add ; // 'plus' se declara como 'template <class T> T plus(T, T) ;' // entonces 'add' es de tipo 'int add( int x, int y )'.func = add ; // OK - Los parámetros y los tipos de retorno son los mismos.int a = func ( 1 , 2 ); // NOTA: si el contenedor 'func' no hace referencia a ninguna función, // se lanza la excepción 'std::bad_function_call'.función < bool ( short , short ) > func2 ; si ( ! func2 ) { // Verdadero porque a 'func2' aún no se le ha asignado una función.bool adyacente ( long x , long y ); func2 = & adyacente ; // OK - Los parámetros y los tipos de retorno son convertibles.struct Test { bool operator ()( short x , short y ); };Test car ; // 'std::ref' es una función plantilla que devuelve el envoltorio // de la función miembro 'operator()' de la estructura 'car'. func = std :: ref ( car ); } func = func2 ; // OK - Los parámetros y los tipos de retorno son convertibles.La clase plantilla functionse definió dentro del archivo de cabecera <functional>, sin necesidad de realizar ningún cambio en el lenguaje C++.
Escriba rasgos para metaprogramación
La metaprogramación consiste en crear un programa que crea o modifica otro programa (o a sí mismo). Esto puede ocurrir durante la compilación o durante la ejecución. El Comité de Estándares de C++ ha decidido introducir una biblioteca para la metaprogramación durante la compilación mediante plantillas.
Aquí hay un ejemplo de un metaprograma que utiliza el estándar C++03: una recursión de instancias de plantillas para calcular exponentes enteros:
plantilla < int B , int N > struct Power { // llamada recursiva y recombinación. enum { VALUE = B * Power < B , N - 1 >:: VALUE }; };plantilla < int B > struct Power < B , 0 > { // ''N == 0'' condición de terminación. enum { VALOR = 1 }; };int quartic_of_three = Potencia < 3 , 4 >:: VALOR ;Muchos algoritmos pueden operar con diferentes tipos de datos; las plantillas de C++ admiten la programación genérica y hacen que el código sea más compacto y útil. Sin embargo, es común que los algoritmos necesiten información sobre los tipos de datos que se utilizan. Esta información se puede extraer durante la instanciación de una clase plantilla mediante rasgos de tipo .
Los rasgos de tipo pueden identificar la categoría de un objeto y todas las características de una clase (o de una estructura). Se definen en el nuevo encabezado <type_traits>.
En el siguiente ejemplo está la función de plantilla 'elaborate' que, dependiendo de los tipos de datos dados, instanciará uno de los dos algoritmos propuestos ( Algorithm::perform).
#include <type_traits>using std :: is_floating_point ; using std :: is_integral ;// Primera forma de operar. plantilla < bool B > struct Algorithm { plantilla < clase T1 , clase T2 > static int perform ( T1 & , T2 & ) { // ... } };// Segunda forma de operar. plantilla <> struct Algorithm < true > { plantilla < clase T1 , clase T2 > static int perform ( T1 , T2 ) { // ... } };// La instanciación de 'elaborate' instanciará automáticamente la forma correcta de operar. template < class T1 , class T2 > int elaborar ( T1 A , T2 B ) { // Use la segunda forma solo si 'T1' es un entero y si 'T2' es // de punto flotante, de lo contrario use la primera forma. return Algorithm < is_integral < T1 >:: value && is_floating_point < T2 >:: value >:: perform ( A , B ) ; }Mediante rasgos de tipo , definidos en el encabezado <type_traits>, también es posible crear operaciones de transformación de tipo ( static_casty const_castson insuficientes dentro de una plantilla).
Este tipo de programación produce código elegante y conciso; sin embargo, el punto débil de estas técnicas es la depuración: resulta engorrosa durante la compilación y muy difícil durante la ejecución del programa.
Método uniforme para calcular el tipo de retorno de los objetos de función.
Determinar el tipo de retorno de un objeto de función plantilla en tiempo de compilación no es intuitivo, especialmente si el valor de retorno depende de los parámetros de la función. Por ejemplo:
struct Clear { // El tipo de parámetro es igual al tipo de retorno. int operator ()( int ) const ; double operator ()( double ) const ; };plantilla < clase Obj > clase Calculus { privado : Obj miembro ; público : plantilla < clase Arg > Arg operador ()( Arg & a ) const { return miembro ( a ); } };Al instanciar la plantilla de clase , el objeto de función de siempre tendrá el mismo tipo de retorno que el objeto de función de . Sin embargo, dada la clase a continuación:Calculus<Clear>calculusClearConfused
struct Confused { // El tipo de parámetro no es igual al tipo de retorno. double operator ()( int ) const ; int operator ()( double ) const ; };Intentar instanciar provocará que el tipo de retorno no sea el mismo que el de la clase . El compilador puede generar advertencias sobre la conversión de a y viceversa.Calculus<Confused>CalculusConfusedintdouble
TR1 introduce, y C++11 adopta, la clase plantilla std::result_ofque permite determinar y usar el tipo de retorno de un objeto función para cada declaración. El objeto CalculusVer2usa el std::result_ofobjeto para derivar el tipo de retorno del objeto función:
#include <type_traits>usando std :: resultado_de ;plantilla < clase Obj > clase CalculusVer2 { privado : Obj miembro ; público : plantilla < clase Arg > nombre de tipo resultado_de < Obj ( Arg ) >:: tipo operador ()( Arg & a ) const { return miembro ( a ); } };De esta forma, en las instancias del objeto de función no hay conversiones, advertencias ni errores.CalculusVer2<Confused>
El único cambio con respecto a la versión TR1 es std::result_ofque esta última permitía que una implementación no pudiera determinar el tipo de resultado de una llamada a función. Debido a los cambios en C++ para admitir decltype, la versión C++11 std::result_ofya no necesita estos casos especiales; las implementaciones deben calcular un tipo en todos los casos.
Compatibilidad mejorada con C
Para compatibilidad con C , a partir de C99, se añadieron estos: [ 27 ]
- Preprocesador: [ 28 ]
- macros variádicas ,
- concatenación de literales de cadena estrecha/ancha adyacentes,
_Pragma()– equivalente de#pragma.
long long– tipo entero de al menos 64 bits de longitud.__func__– macro que se evalúa al nombre de la función en la que se encuentra.- Encabezados:
cstdbool(stdbool.h),cstdint(stdint.h),cinttypes(inttypes.h).
Características originalmente planeadas pero eliminadas o no incluidas
Dirigiéndonos a un TR aparte:
Aplazado:
- Conceptos
- Soporte más completo o requerido para la recolección de basura
- Reflexión
- Telescopios macro
Funcionalidades eliminadas o descontinuadas
Se eliminó el término punto de secuencia y se reemplazó por la especificación de que una operación se secuencia antes que otra, o que dos operaciones no se secuencian. [ 30 ]
Se eliminó el uso de la palabra clave export, junto con las plantillas exportadas . [ 31 ] La exportpalabra clave permaneció reservada y posteriormente se reintrodujo en C++20 con la adición de módulos .
Las especificaciones de excepciones dinámicas están obsoletas. [ 31 ] La especificación en tiempo de compilación de funciones que no lanzan excepciones está disponible con la noexceptpalabra clave, lo cual es útil para la optimización.
std::auto_ptrestá obsoleto, habiendo sido reemplazado por std::unique_ptr.
Las clases base de objetos de función ( std::unary_function, std::binary_function), los adaptadores a punteros a funciones y los adaptadores a punteros a miembros, y las clases de enlace están todas obsoletas.
El Anexo D.2 establece: "El uso de la registerpalabra clave como especificador de clase de almacenamiento (§7.1.1) está obsoleto."
Véase también
Referencias
- ↑ "Tenemos un estándar internacional: C++0x está aprobado por unanimidad" . 12 de agosto de 2011. Archivado del original el 11 de diciembre de 2018. Recuperado el 12 de agosto de 2011 .
- 1 2 Stroustrup, Bjarne. "Preguntas frecuentes sobre C++11" . stroustrup.com . Archivado del original el 6 de octubre de 2018. Consultado el 15 de octubre de 2014 .
- 1 2 "Descripción general de C++11: ¿Qué objetivos de diseño específicos guiaron al comité?" . Estándar C++ . Archivado del original el 31-01-2019 . Recuperado el 04-09-2015 .
- ↑ "Bjarne Stroustrup: Una visión general de C++0x" (PDF) . Archivado (PDF) del original el 17 de junio de 2016. Recuperado el 30 de junio de 2011 .
- ↑ "ISO/IEC 14882:2011" . ISO. 2 de septiembre de 2011. Archivado del original el 29 de enero de 2013. Consultado el 3 de septiembre de 2011 .
- ↑ "Borrador de trabajo, estándar para el lenguaje de programación C++" (PDF) . Archivado (PDF) del original el 21/01/2019 . Consultado el 26/04/2012 .
- ↑ "El Estándar" . Fundación del Estándar C++ . Archivado del original el 5 de noviembre de 2012. Consultado el 2 de noviembre de 2012. Excepto por los estándares/informes finales, todos los documentos del comité de C++ están disponibles públicamente, incluidos todos los borradores de
trabajo, muchos de los cuales se aproximan bastante al estándar publicado. El borrador de trabajo de enero de 2012 contiene el estándar C++11 más cambios editoriales menores.
- ↑ "Clang - Estado del lenguaje de programación C++" . 29/11/2023. Archivado del original el 29/11/2023 . Consultado el 01/12/2023 .
- ↑ "GCC 4.8.1 lanzado, característica C++11 completa: C++ estándar" . isocpp.org . Consultado el 1 de diciembre de 2023 .
- ^ Sutter, Alexandrescu "Estándares de codificación C++" #15
- ↑ Gabriel Dos Reis; Bjarne Stroustrup (22 de marzo de 2010). "Expresiones constantes generales para lenguajes de programación de sistemas, Actas SAC '10" (PDF) . Archivado (PDF) del original el 13 de junio de 2018. Recuperado el 18 de agosto de 2012 .
- ↑ Jaakko Järvi; Bjarne Stroustrup; Douglas Gregor; Jeremy Siek (28 de abril de 2003). "Decltype y auto, lenguaje de programación C++, documento n.º: N1478=03-0061" (PDF) . Archivado (PDF) del original el 28 de mayo de 2015. Recuperado el 6 de junio de 2015 .
- ↑ Roger Orr (junio de 2013) ."Automóvil: ¿un mal necesario?" Overload Journal #115" . Archivado del original el 6 de junio de 2015. Consultado el 6 de junio de 2015 .
- ↑ "Documento n.º N1968=06-0038 - Expresiones lambda y cierres para C++" (PDF) . Estándares abiertos. Archivado (PDF) del original el 28/07/2011 . Consultado el 20/04/2009 .
- ↑ "Decltype (revisión 5)" (PDF) . Archivado (PDF) del original el 14-02-2022 . Recuperado el 16-02-2022 .
- ↑ "especificador automático (desde C++11) - cppreference.com" . en.cppreference.com . Archivado del original el 20 de octubre de 2016. Consultado el 18 de octubre de 2016 .
- ↑ Gustedt, Jens (09/07/2019). "Introducción a la constante nullptr - v1" (PDF) . Registro de documentos ISO JTC1/SC22/WG14 . Organización Internacional de Normalización. Archivado (PDF) del original el 27/07/2020 . Recuperado el 19/04/2020 a través de open-std.org.
- ↑ Esto causó un conflicto con el uso propuesto (común en otros lenguajes) del guion bajo para agrupar dígitos en literales numéricos como los literales enteros , por lo que C++14 en su lugar usa el apóstrofo (como una coma mayúscula ) para agrupar. Daveed Vandevoorde (2012-09-21). "N3448: Separación de dígitos sin dolor" (PDF) . Archivado (PDF) del original el 11 de agosto de 2015. Recuperado el 13 de agosto de 2015 ., Lawrence Crowl (19 de diciembre de 2012). "N3499: Separadores de dígitos" . Archivado del original el 11 de agosto de 2015. Recuperado el 13 de agosto de 2015 .
- ↑ ISO / IEC (2003). ISO/IEC 14882 :2003(E): Lenguajes de programación – C++ §3.2 Regla de una definición [basic.def.odr] párr. 3
- 1 2 "Funciones predeterminadas y eliminadas – ISO/IEC JTC1 SC22 WG21 N2210 = 07-0070 – 11-03-2007" . Archivado del original el 19-08-2012 . Recuperado el 20-12-2012 .
- ↑ "Uso de la colección de compiladores GNU (GCC): Long Long" . gcc.gnu.org . Archivado del original el 21 de agosto de 2016. Consultado el 25 de julio de 2016 .
- ↑ "Rangos de tipos de datos (C++)" . Archivado del original el 21/02/2009 . Consultado el 23/04/2009 .
- ↑ Samuel P. Harbison III, Guy L. Steele Jr.: "C – Un manual de referencia", 5.ª edición, pág. 251
- ↑ JF Bastien; Alisdair Meredith (16 de abril de 2021). "Eliminación del soporte para la recolección de basura" .
- ↑ Milewski, Bartosz (3 de marzo de 2009). "Promesas rotas: futuros de C++0x" . Archivado del original el 16 de septiembre de 2011. Recuperado el 24 de enero de 2010 .
- ↑ "Biblioteca de expresiones regulares de C++" . cppreference.com . Consultado el 10 de diciembre de 2022 .
- ↑ "Clang - Estado de C++98, C++11 y C++14" . Clang.llvm.org. 12 de mayo de 2013. Archivado del original el 28 de mayo de 2019. Consultado el 10 de junio de 2013 .
- ↑ "Cambios preliminares para la sincronización del preprocesador C99" . www.open-std.org . Archivado del original el 31 de julio de 2020. Consultado el 26 de mayo de 2014 .
- ^ Daveed Vandevoorde (11 de enero de 2011). "Módulos en C++" (PDF) . open-std.org . GT21.
- ↑ Caves, Jonathan (4 de junio de 2007). "Actualización sobre el estándar del lenguaje C++-0x" . Archivado del original el 9 de septiembre de 2011. Recuperado el 25 de mayo de 2010 .
- 1 2 Sutter, Herb (3 de marzo de 2010). "Informe de viaje: Reunión de estándares ISO C++ de marzo de 2010" . Archivado del original el 11 de julio de 2018. Recuperado el 24 de marzo de 2010 .
Enlaces externos
- El Comité de Estándares de C++
- C++0X: La nueva cara del estándar C++
- Cobertura del blog de Herb Sutter sobre C++11
- Cobertura del blog de Anthony Williams sobre C++11
- Charla sobre C++0x impartida por Bjarne Stroustrup en la Universidad de Waterloo. Archivada el 23 de enero de 2009 en la Wayback Machine.
- El estado del idioma: Entrevista con Bjarne Stroustrup (15 de agosto de 2008) Archivada el 31 de enero de 2009 en la Wayback Machine.
- Página wiki para ayudar a realizar un seguimiento de las características principales del lenguaje C++ 0x y su disponibilidad en los compiladores.
- Referencia en línea de la biblioteca estándar de C++11
- Compilador C++11 en línea
- Preguntas frecuentes sobre C++11 de Bjarne Stroustrup
- Más información sobre las características de C++11: bucles for basados en rangos, por qué auto_ptr está obsoleto, etc.
- C++
- estándares de lenguajes de programación
- Familia de lenguajes de programación C++
- Normas IEC
- Normas ISO