C ++ 11 es una versión del estándar para el lenguaje de programación C ++ . Fue aprobado por la Organización Internacional de Normalización (ISO) el 12 de agosto de 2011, reemplazando a C ++ 03 , [1] reemplazado por C ++ 14 el 18 de agosto de 2014 [2] y posteriormente, por C ++ 17 . El nombre sigue la tradición de nombrar las versiones del idioma según el año de publicación de la especificación, aunque anteriormente se llamaba C ++ 0x porque se esperaba que se publicara antes de 2010. [3]
Aunque uno de los objetivos del diseño era preferir los cambios en las bibliotecas sobre los cambios en el lenguaje central , [4] C ++ 11 hace varias adiciones al lenguaje central. Las áreas del lenguaje central que se mejoraron significativamente incluyen soporte de subprocesos múltiples, soporte de programación genérica , inicialización uniforme y rendimiento. También se hicieron cambios significativos a la ++ biblioteca estándar de C , que incorpora la mayor parte de los 1 C ++ Informe Técnico (TR1) bibliotecas , salvo la biblioteca de funciones especiales matemáticos. [5]
C ++ 11 se publicó como ISO / IEC 14882: 2011 [6] en septiembre de 2011 y está disponible por una tarifa. El borrador de trabajo más similar al estándar C ++ 11 publicado es N3337, con fecha del 16 de enero de 2012; [7] solo tiene correcciones editoriales del estándar C ++ 11. [8]
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 compatibilidad con C ++ 98 y posiblemente con C
- Prefiere introducir nuevas funciones a través de la biblioteca estándar, en lugar de ampliar el lenguaje principal.
- Prefiere cambios que puedan evolucionar la técnica de programación.
- Mejorar C ++ para facilitar el diseño de bibliotecas y sistemas, en lugar de introducir nuevas funciones útiles solo para aplicaciones específicas.
- Aumente la seguridad de los tipos al proporcionar alternativas más seguras a las técnicas inseguras anteriores
- Aumente el rendimiento y la capacidad de trabajar directamente con hardware.
- Proporcionar soluciones adecuadas para problemas del mundo real.
- Implementar el principio de cero gastos generales (el soporte adicional que necesitan algunas empresas de servicios públicos debe usarse solo si se usa la utilidad)
- Haga que C ++ sea fácil de enseñar y aprender sin eliminar ninguna utilidad que necesiten los programadores expertos
La atención a los principiantes se considera importante, 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. [1] [ verificación fallida ]
Extensiones al lenguaje central C ++
Una función del comité de C ++ es el desarrollo del núcleo del lenguaje. Las áreas del lenguaje central que se mejoraron significativamente incluyen soporte de subprocesos múltiples , soporte de programación genérica , inicialización uniforme y rendimiento.
Mejoras en el rendimiento del tiempo de ejecución del lenguaje principal
Estas características del lenguaje existen principalmente para proporcionar algún tipo de beneficio de rendimiento, ya sea de memoria o de velocidad computacional. [ cita requerida ]
Rvalue referencias y constructores de movimientos
En C ++ 03 (y antes), los temporales (denominados " rvalues ", ya que a menudo se encuentran en el lado derecho de una asignación) estaban destinados a nunca ser modificables, al igual que en C, y se consideraban indistinguibles de los const T&
tipos; sin embargo, en algunos casos, los temporales podrían haber sido modificados, comportamiento que incluso se consideró una laguna útil. [9] C ++ 11 agrega un nuevo tipo de referencia no constante llamadorvalue referencia , identificado porT&&
. Esto se refiere a los temporales que pueden modificarse después de que se inicializan, con el fin de permitir la "semántica de movimiento".
Un problema de rendimiento crónico con C ++ 03 son las copias profundas costosas e innecesarias que pueden ocurrir implícitamente cuando los objetos se pasan por valor. Para ilustrar el problema, considere que an std::vector
es, internamente, un envoltorio alrededor de una matriz de estilo C con un tamaño definido. Si std::vector
se crea un temporal o se devuelve desde una función, solo se puede almacenar creando uno nuevo std::vector
y copiando todos los datos de rvalue en él. Entonces se destruye lo temporal y toda su memoria. (Para simplificar, esta discusión descuida la optimización del valor de retorno ).
En C ++ 11, un mover el constructor destd::vector
eso toma una referencia rvalue a ystd::vector
puede copiar el puntero a la matriz interna de estilo C fuera del rvalue al nuevostd::vector
, luego establezca el puntero dentro del rvalue en nulo. Dado que el temporal nunca se volverá a utilizar, ningún código intentará acceder al puntero nulo y, debido a que el puntero es nulo, su memoria no se borra cuando sale del alcance. Por lo tanto, la operación no solo evita el gasto de una copia profunda, sino que es segura e invisible.
Las referencias de Rvalue pueden proporcionar beneficios de rendimiento al código existente sin necesidad de realizar cambios fuera de la biblioteca estándar. El tipo de valor devuelto de una función que devuelve un std::vector
temporal no necesita cambiarse explícitamente std::vector
para invocar el constructor de movimiento, ya que los temporales se consideran rvalues automáticamente. (Sin embargo, si std::vector
es una versión de C ++ 03 sin un constructor de movimiento, entonces el constructor de copia se invocará con un const std::vector
, incurriendo en una asignación de memoria significativa).
Por razones de seguridad, se imponen algunas restricciones. Una variable nombrada nunca se considerará un rvalue incluso si se declara como tal. Para obtener un rvalue, se std::move()
debe usar la plantilla de función . Las referencias de Rvalue también se pueden modificar solo en determinadas circunstancias, y están destinadas a utilizarse principalmente con constructores de movimientos.
Debido a la naturaleza de la redacción de las referencias de rvalue, y a algunas modificaciones de la redacción de las referencias de lvalue (referencias regulares), las referencias de rvalue permiten a los desarrolladores proporcionar un reenvío de funciones perfecto. Cuando se combina con plantillas variadas , esta capacidad permite plantillas de funciones que pueden reenviar perfectamente argumentos a otra función que toma esos argumentos particulares. Esto es más útil para reenviar los parámetros del constructor, para crear funciones de fábrica que llamarán automáticamente al constructor correcto para esos argumentos en particular. Esto se ve en el conjunto emplace_back de los métodos de biblioteca estándar de C ++.
constexpr - Expresiones constantes generalizadas
C ++ siempre ha tenido el concepto de expresiones constantes. Estas son expresiones como las 3+4
que siempre producirán los mismos resultados, en tiempo de compilación y en tiempo de ejecución. Las expresiones constantes son oportunidades de optimización para los compiladores, y los compiladores las ejecutan con frecuencia en tiempo de compilación y codifican los resultados en el programa. Además, en varios lugares, la especificación C ++ requiere el uso de expresiones constantes. La definición de una matriz requiere una expresión constante y los valores del enumerador deben ser expresiones constantes.
Sin embargo, nunca se ha permitido que una expresión constante contenga una llamada de función o un constructor de objeto. Entonces, un fragmento de código tan simple como este no es válido:
int get_five () { return 5 ;}int algún_valor [ get_five () + 7 ]; // Crea una matriz de 12 enteros. C ++ mal formado
Esto no era válido en C ++ 03 porque get_five() + 7
no es una expresión constante. Un compilador de C ++ 03 no tiene forma de saber si get_five()
realmente es constante en tiempo de ejecución. En teoría, esta función podría afectar una variable global, llamar a otras funciones constantes que no sean de tiempo de ejecución, etc.
C ++ 11 introdujo la palabra clave constexpr
, que permite al usuario garantizar que una función o un constructor de objetos es una constante en tiempo de compilación. [10] El ejemplo anterior se puede reescribir de la siguiente manera:
constexpr int get_five () { return 5 ;}int algún_valor [ get_five () + 7 ]; // Crea una matriz de 12 enteros. C ++ 11 válido
Esto permite que el compilador comprenda y verifique que get_five()
es una constante en tiempo de compilación.
El uso constexpr
de una función impone algunos límites a lo que puede hacer esa función. Primero, la función debe tener un tipo de retorno no nulo. En segundo lugar, el cuerpo de la función no puede declarar variables ni definir nuevos tipos. En tercer lugar, el cuerpo puede contener solo declaraciones, declaraciones nulas y una única declaración de retorno. Deben existir valores de argumento tales que, después de la sustitución de argumentos, la expresión en la declaración de retorno produzca una expresión constante.
Antes de C ++ 11, los valores de las variables podían usarse en expresiones constantes solo si las variables se declaran const, tienen un inicializador que es una expresión constante y son de tipo integral o enumeración. C ++ 11 elimina la restricción de que las variables deben ser de tipo integral o enumeración si se definen con la constexpr
palabra clave:
constexpr double earth_gravitational_acceleration = 9.8 ; constexpr doble aceleración_gravitacional_luna = aceleración_gravitacional_tierra / 6.0 ;
Tales 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 expresión constante a partir de tipos definidos por el usuario, los constructores también se pueden declarar con constexpr
. constexpr
El cuerpo de la función de un constructor solo puede contener declaraciones y declaraciones nulas, y no puede declarar variables o definir tipos, como ocurre con una constexpr
función. Deben existir valores de argumento tales que, después de la sustitución de argumentos, inicialice los miembros de la clase con expresiones constantes. Los destructores de estos tipos deben ser triviales.
El constructor de copia para un tipo con cualquier constexpr
constructor generalmente también debe definirse como un constexpr
constructor, para permitir que los objetos del tipo sean devueltos por valor de una función constexpr. Cualquier función miembro de una clase, como constructores de copia, sobrecargas de operadores, etc., puede declararse como constexpr
, siempre que cumplan los requisitos de las funciones constexpr. Esto permite al compilador copiar objetos en tiempo de compilación, realizar operaciones en ellos, etc.
Si se llama a una función o constructor 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 declaración de retorno de una función constexpr no se evalúa como una expresión constante para una invocación determinada, el resultado no es una expresión constante.
constexpr
difiere de consteval
, introducido en C ++ 20 , en que este último siempre debe producir una constante de tiempo de compilación, mientras constexpr
que no tiene esta restricción.
Modificación de la definición de datos antiguos sin formato
En C ++ 03, una clase o estructura debe seguir una serie de reglas para que se considere un tipo de datos antiguos simples (POD). Los tipos que se ajustan a esta definición producen diseños de objetos que son compatibles con C y también podrían inicializarse estáticamente. El estándar C ++ 03 tiene restricciones sobre qué tipos son compatibles con C o pueden inicializarse estáticamente a pesar de que no hay ninguna razón técnica por la que un compilador no pueda aceptar el programa; si alguien creara un tipo C ++ 03 POD y agregara una función miembro no virtual, este tipo ya no sería un tipo POD, no se podría inicializar estáticamente y sería incompatible con C a pesar de que no haya cambios en el diseño de la memoria .
C ++ 11 relajó varias de las reglas de POD, al dividir el concepto de POD en dos conceptos separados: diseño trivial y estándar .
Un tipo que es trivial se puede inicializar estáticamente. También significa que es válido copiar datos a través de memcpy
, en lugar de tener que usar un constructor de copias. La vida útil de un tipo trivial comienza cuando se define su almacenamiento, no cuando se completa un constructor.
Una clase o estructura trivial se define como aquella que:
- Tiene un constructor predeterminado trivial. Esto puede usar la sintaxis de constructor predeterminada (
SomeConstructor() = default;
). - Tiene constructores triviales de copiar y mover, que pueden usar la sintaxis predeterminada.
- Tiene operadores triviales de asignación de copia y movimiento, que pueden usar 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 copiar / mover también requieren que todos los miembros de datos no estáticos sean triviales.
Un tipo que es de diseño estándar significa que ordena y empaqueta sus miembros de una manera que es compatible con C.Una clase o estructura es de 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, incluido cualquiera de sus clases base, están en la misma clase en 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 POD si es trivial, de diseño estándar y todos sus miembros de datos no estáticos y clases base son POD.
Al separar estos conceptos, es posible renunciar a uno sin perder el otro. Una clase con constructores complejos de movimiento y copia puede no ser trivial, pero podría ser de diseño estándar y, por lo tanto, interoperar con C.De manera similar, una clase con miembros de datos no estáticos públicos y privados no sería de diseño estándar, pero podría ser trivial y por lo tanto memcpy
-able.
Mejoras en el rendimiento en el tiempo de construcción del lenguaje principal
Plantilla externa
En C ++ 03, el compilador debe crear una instancia de una plantilla siempre que se encuentre una plantilla completamente especificada en una unidad de traducción. Si se crea una instancia de la plantilla con los mismos tipos en muchas 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 declaraciones de plantilla externas, análogas a las declaraciones de datos externas.
C ++ 03 tiene esta sintaxis para obligar al compilador a crear una instancia de una plantilla:
plantilla clase std :: vector < MyClass > ;
C ++ 11 ahora proporciona esta sintaxis:
extern template class std :: vector < MyClass > ;
que le dice al compilador que no cree una instancia de la plantilla en esta unidad de traducción.
Mejoras en la usabilidad del lenguaje principal
Estas funciones existen con el propósito principal de facilitar el uso del idioma. Estos pueden mejorar la seguridad de los tipos, minimizar la repetición de códigos, hacer que el código erróneo sea menos probable, etc.
Listas de inicializadores
C ++ 03 heredó la característica de lista de inicializadores de C. A una estructura o matriz se le da una lista de argumentos entre llaves, en el orden de las definiciones de los miembros en la estructura. Estas listas de inicializadores son recursivas, por lo que una matriz de estructuras o estructuras que contengan otras estructuras pueden usarlas.
struct Object { flotar primero ; int segundo ; };Objeto escalar = { 0.43f , 10 }; // Un objeto, con primero = 0.43f y segundo = 10 Object anArray [] = {{ 13.4f , 3 }, { 43.28f , 29 }, { 5.934f , 17 }}; // Una matriz de tres objetos
Esto 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 inicializadores. Sin embargo, C ++ 03 permite listas de inicializadores solo en estructuras y clases que se ajustan a la definición de Plain Old Data (POD); C ++ 11 extiende las listas de inicializadores, por lo que se pueden usar para todas las clases, incluidos los contenedores estándar como std::vector
.
C ++ 11 vincula el concepto a una plantilla, llamada std::initializer_list
. Esto permite que los constructores y otras funciones tomen listas de inicializadores como parámetros. Por ejemplo:
class SequenceClass { public : SequenceClass ( std :: initializer_list < int > lista ); };
Esto permite SequenceClass
que se construya a partir de una secuencia de números enteros, como:
SequenceClass some_var = { 1 , 4 , 5 , 6 };
Este constructor es un tipo especial de constructor, llamado constructor de lista de inicializadores. Las clases con un constructor de este tipo se tratan especialmente durante la inicialización uniforme (ver más abajo )
La clase de plantilla std::initializer_list<>
es un tipo de biblioteca estándar C ++ 11 de primera clase . Pueden ser construidos estáticamente por el compilador de C ++ 11 mediante el uso de la {}
sintaxis sin un nombre de tipo en contextos donde tales llaves se deducirán de un std::initializer_list
, o especificando explícitamente el tipo como std::initializer_list
(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 actuará como una copia por referencia (la clase se implementa típicamente como un par de punteros de inicio / fin). An std::initializer_list
es constante: sus miembros no se pueden cambiar una vez que se crea, y tampoco se pueden cambiar los datos de esos miembros (lo que descarta moverse desde ellos, requerir copias en los miembros de la clase, etc.).
Aunque su construcción es tratada especialmente por el compilador, an std::initializer_list
es un tipo real, por lo que puede usarse en otros lugares además de los constructores de clases. Las funciones regulares pueden tomar std::initializer_list
s escritos como argumentos. Por ejemplo:
vacío function_name ( std :: initializer_list < float > lista ); // Copiar es barato; véase más arribafunction_name ({ 1.0f , -3.45f , -0.4f });
Ejemplos de esto en la biblioteca estándar incluyen las plantillas std::min()
y que std::max()
toman std::initializer_list
s de tipo numérico.
Los contenedores estándar también se pueden inicializar de estas formas:
std :: vector < std :: string > v = { "xyzzy" , "plugh" , "abracadabra" }; std :: vector < std :: string > v ({ "xyzzy" , "plugh" , "abracadabra" }); std :: vector < std :: string > v { "xyzzy" , "plugh" , "abracadabra" }; // ver "Inicialización uniforme" a continuación
Inicialización uniforme
C ++ 03 tiene varios problemas con la inicialización de tipos. Existen varias formas de hacer esto, y algunas producen resultados diferentes cuando se intercambian. La sintaxis del constructor tradicional, por ejemplo, puede verse como una declaración de función, y se deben tomar medidas para garantizar que la regla de análisis sintáctica más molesta del compilador no la confunda con tal. Solo los tipos agregados y POD se pueden inicializar con inicializadores agregados (usando SomeType var = {/*stuff*/};
).
C ++ 11 proporciona una sintaxis que permite una inicialización de tipo completamente uniforme que funciona en cualquier objeto. Amplía la sintaxis de la lista de inicializadores:
struct BasicStruct { int x ; doble y ; };struct AltStruct { AltStruct ( int x , doble y ) : x_ { x } , y_ { y } {}privado : int x_ ; doble y_ ; };BasicStruct var1 { 5 , 3.2 }; AltStruct var2 { 2 , 4.3 };
La inicialización de se var1
comporta exactamente como si fuera una inicialización agregada. Es decir, cada miembro de datos de un objeto, a su vez, se inicializará con copia con el valor correspondiente de la lista de inicializadores. La conversión de tipo implícita se utilizará cuando sea necesario. Si no existe conversión, o solo existe una conversión de restricción, el programa está mal formado. La inicialización de var2
invoca al constructor.
También se puede hacer esto:
struct IdString { std :: nombre de la cadena ; int identifier ; }; IdString get_string () { return { "foo" , 42 }; // Tenga en cuenta la falta de tipo explícito. }
La inicialización uniforme no reemplaza la sintaxis del constructor, que todavía es necesaria a veces. Si una clase tiene un constructor de lista de inicializadores ( TypeName(initializer_list
), entonces tiene prioridad sobre otras formas de construcción, siempre que la lista de inicializadores se ajuste al tipo del constructor de secuencia. La versión C ++ 11 de std::vector
tiene un constructor de lista de inicializadores para su tipo de plantilla. Por lo tanto, este código:
std :: vector < int > the_vec { 4 };
llamará al constructor de la lista de inicializadores, no al constructor de std::vector
que toma un parámetro de tamaño único y crea el vector con ese tamaño. Para acceder al último constructor, el usuario deberá utilizar directamente la sintaxis del constructor estándar.
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 plantillas y las técnicas de metaprogramación de plantillas, es posible que el tipo de algo, en particular el valor de retorno bien definido de una función, no se exprese fácilmente. Por lo tanto, almacenar intermedios en variables es difícil, posiblemente necesitando conocimiento de los aspectos internos de una biblioteca de metaprogramación determinada.
C ++ 11 permite mitigar esto de dos maneras. Primero, la definición de una variable con una inicialización explícita puede usar la auto
palabra clave. [11] [12] Esto crea una variable del tipo específico de inicializador:
auto some_strange_callable_type = std :: bind ( & some_function , _2 , _1 , some_object ); auto otra_variable = 5 ;
El tipo de some_strange_callable_type
es simplemente cualquiera que la función de plantilla particular anule los std::bind
retornos para esos argumentos particulares. El compilador determina fácilmente este tipo de forma procedimental como parte de sus funciones de análisis semántico, pero no es fácil de determinar para el usuario tras la inspección. El tipo de other_variable
tambié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 auto
en C ++ rediseña la semántica de esta palabra clave, que se usó originalmente en el lenguaje B predecesor sin tipo en un rol relacionado de denotar una definición de variable automática sin tipo .
Además, la palabra clave decltype
se puede utilizar para determinar el tipo de expresión en tiempo de compilación. Por ejemplo:
int some_int ; decltype ( some_int ) other_integer_variable = 5 ;
Esto es más útil junto con auto
, ya que el tipo de variable automática solo lo conoce el compilador. Sin embargo, decltype
también puede ser muy útil para expresiones en código que hacen un uso intensivo de la sobrecarga de operadores y tipos especializados.
auto
también es útil para reducir la verbosidad del código. Por ejemplo, en lugar de escribir
para ( std :: vector < int > :: const_iterator itr = myvec . cbegin (); itr ! = myvec . cend (); ++ itr )
el programador puede usar el más corto
para ( auto itr = myvec . cbegin (); itr ! = myvec . cend (); ++ itr )
que se puede compactar aún más ya que "myvec" implementa iteradores de inicio / fin:
para ( auto & x : myvec )
Esta diferencia crece a medida que el programador comienza a anidar contenedores, aunque en tales casos los typedef
s son una buena forma de disminuir la cantidad de código.
El tipo denotado por decltype
puede ser diferente del tipo deducido por auto
.
#include int main () { const std :: vector < int > v ( 1 ); auto a = v [ 0 ]; // a tiene tipo int decltype ( v [ 0 ]) b = 1 ; // b tiene el tipo const int &, el tipo de retorno de // std :: vector :: operator [] (size_type) const auto c = 0 ; // c tiene tipo int auto d = c ; // d tiene tipo int decltype ( c ) e ; // e tiene el 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 rango
C ++ 11 extiende la sintaxis de la for
declaración para permitir una fácil iteración sobre una variedad de elementos:
int my_array [ 5 ] = { 1 , 2 , 3 , 4 , 5 }; // duplica el valor de cada elemento en my_array: for ( int & x : my_array ) x * = 2 ;// similar pero también usando inferencia de tipo para elementos de matriz para ( auto & x : my_array ) x * = 2 ;
Esta forma de for
, llamada "basada en rango para", iterará sobre cada elemento de la lista. Se trabajará para las matrices de tipo C, inicializador listas, y cualquier tipo que tiene begin()
y end()
funciones definidas por ello que los iteradores de retorno. Todos los contenedores de biblioteca estándar que tienen 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. [13] Se definen como sigue:
[] ( int x , int y ) -> int { return x + y ; }
El tipo de retorno ( -> int
en este ejemplo) se puede omitir siempre que todas las return
expresiones devuelvan el mismo tipo. Opcionalmente, una lambda puede ser un cierre .
Sintaxis de función alternativa
La sintaxis de declaración de función estándar de C era perfectamente adecuada para el conjunto de características del lenguaje C. A medida que C ++ evolucionó de C, mantuvo la sintaxis básica y la extendió donde fuera necesario. Sin embargo, a medida que C ++ se hizo más complejo, expuso varios límites, especialmente con respecto a las declaraciones de funciones de plantilla. Por ejemplo, en C ++ 03 esto no está permitido:
plantilla < clase Lhs , clase Rhs > Ret adding_func ( const Lhs y LHS , const RHS y RHS ) { retorno LHS + rhs ;} // Ret debe ser del tipo de LHS + RHS
El tipo Ret
es cualquiera que sea la adición de tipos Lhs
y Rhs
producirá. Incluso con la funcionalidad de C ++ 11 mencionada anteriormente decltype
, esto no es posible:
plantilla < clase LHS , clase RHS > decltype ( LHS + rhs ) adding_func ( const Lhs y LHS , const Rhs y rhs ) { retorno LHS + RHS ;} // No es válido C ++ 11
Esto no es válido para C ++ porque lhs
y rhs
aún no se ha definido; no serán identificadores válidos hasta que el analizador haya analizado el resto del prototipo de función.
Para solucionar esto, C ++ 11 introdujo una nueva sintaxis de declaración de función, con un tipo de retorno al final :
plantilla < clase Lhs , clase Rhs > Auto adding_func ( const Lhs y LHS , const Rhs y RHS ) -> decltype ( izq + RHS ) { retorno izda + RHS ;}
Esta sintaxis se puede utilizar para declaraciones y definiciones de funciones más mundanas:
struct SomeStruct { auto func_name ( int x , int y ) -> int ; };auto SomeStruct :: func_name ( int x , int y ) -> int { return x + y ; }
El uso de la palabra clave "auto" en este caso es solo una parte de la sintaxis y no realiza la 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. [14]
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 inicializadores de esa clase. Cada constructor debe construir todos sus miembros de clase por sí mismo o llamar a una función miembro común, de la siguiente manera:
class SomeType { public : SomeType ( int new_number ) { Construct ( new_number ); } SomeType () { Construct ( 42 ); }private : void Construct ( int new_number ) { number = new_number ; } int número ; };
Los constructores para clases base no pueden exponerse directamente a clases derivadas; cada clase derivada debe implementar constructores incluso si un constructor de clase base sería apropiado. Los miembros de datos no constantes de las clases no se pueden inicializar en el sitio de la declaración de esos miembros. Solo se pueden inicializar en un constructor.
C ++ 11 proporciona soluciones a todos estos problemas.
C ++ 11 permite a los constructores llamar a otros constructores pares (denominada delegación ). Esto permite a los constructores utilizar el comportamiento de otro constructor con un mínimo de código agregado. La delegación se ha utilizado en otros lenguajes, por ejemplo, Java y Objective-C .
Esta sintaxis es la siguiente:
clase SomeType { int número ;public : SomeType ( int new_number ) : number ( new_number ) {} SomeType () : SomeType ( 42 ) {} };
Tenga en cuenta que, en este caso, se podría haber logrado el mismo efecto haciendo new_number
un parámetro predeterminado. La nueva sintaxis, sin embargo, permite que el valor predeterminado (42) se exprese en la implementación en lugar de la interfaz, un beneficio para los mantenedores del código de la biblioteca, ya que los valores predeterminados para los parámetros de función están "integrados" para llamar a sitios, mientras que la delegación del constructor permite el valor que se va a cambiar sin recompilar el código usando la biblioteca.
Esto viene con una advertencia: C ++ 03 considera que un objeto se construye cuando su constructor termina de ejecutarse, pero C ++ 11 considera un objeto construido una vez que cualquier constructor finaliza la ejecución. Dado que se permitirá la ejecución de múltiples constructores, esto significará que cada constructor delegante se ejecutará en un objeto completamente construido de su propio tipo. Los constructores de clases derivadas se ejecutarán después de que se complete toda la delegación en sus clases base.
Para los constructores de clase base, C ++ 11 permite que una clase especifique que se heredarán los constructores de la clase base. 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 todos los constructores de esa clase base se reenvían o ninguno de ellos. Además, un constructor heredado será sombreado si coincide con la firma de un constructor de la clase derivada, y existen restricciones para la herencia múltiple: los constructores de clases no se pueden heredar de dos clases que usan constructores con la misma firma .
La sintaxis es la siguiente:
class BaseClass { public : BaseClass ( valor int ); };clase DerivedClass : public BaseClass { public : using BaseClass :: BaseClass ; };
Para la inicialización de miembros, C ++ 11 permite esta sintaxis:
class SomeClass { public : SomeClass () {} explícita SomeClass ( int new_value ) : value ( new_value ) {}privado : valor int = 5 ; };
Cualquier constructor de la clase se inicializará value
con 5, si el constructor no anula la inicialización con la suya propia. Entonces, el constructor vacío anterior se inicializará value
como indica la definición de la clase, pero el constructor que toma un int lo inicializará con el parámetro dado.
También puede usar constructor o inicialización uniforme, en lugar de la inicialización de asignación que se muestra arriba.
Anulaciones explícitas y final
En C ++ 03, es posible crear accidentalmente una nueva función virtual, cuando se pretendía anular una función de clase base. Por ejemplo:
struct Base { virtual void some_func ( float ); };struct Derived : Base { virtual void some_func ( int ); };
Suponga que Derived::some_func
está destinado a reemplazar la versión de la clase base. Pero en cambio, debido a que tiene una firma diferente , crea una segunda función virtual. Este es un problema común, particularmente cuando un usuario va a modificar la clase base.
C ++ 11 proporciona sintaxis para resolver este problema.
struct Base { virtual void some_func ( float ); };struct Derived : Base { virtual void some_func ( int ) override ; // mal formado - no anula un método de clase base };
El override
identificador especial significa que el compilador verificará la (s) clase (s) base para ver si hay una función virtual con esta firma exacta. Y si no lo hay, el compilador indicará un error.
C ++ 11 también agrega la capacidad de evitar heredar de clases o simplemente prevenir métodos de reemplazo en clases derivadas. Esto se hace con el identificador especial final
. Por ejemplo:
struct Base1 final { };estructura Derived1 : Base1 { }; // mal formado porque la clase Base1 se ha marcado como final
struct Base2 { virtual void f () final ; };estructura Derived2 : Base2 { void f (); // mal formado porque la función virtual Base2 :: f ha sido marcada como final };
En este ejemplo, la virtual void f() final;
instrucción declara una nueva función virtual, pero también evita que las clases derivadas la anulen. También tiene el efecto de evitar que las clases derivadas usen esa combinación de parámetro y nombre de función particular.
Tenga en cuenta que ni override
tampoco final
son palabras clave de idioma. Técnicamente, son identificadores de los atributos del declarador:
- obtienen un significado especial como atributos solo cuando se usan en esos contextos finales específicos (después de todos los especificadores de tipo, especificadores de acceso, declaraciones de miembros (para tipos de estructura, clase y enumeración) y especificadores de declaradores, pero antes de la inicialización o implementación del código de cada declarador en una coma -lista separada de declaradores);
- no alteran la firma de tipo declarada y no declaran ni anulan ningún identificador nuevo en ningún ámbito;
- Los atributos de declarador reconocidos y aceptados pueden extenderse en versiones futuras de C ++ (algunas extensiones específicas del compilador ya reconocen atributos de declarador agregados, para proporcionar opciones de generación de código o sugerencias de optimización para el compilador, o para generar datos agregados en el código compilado, destinado a depuradores, enlazadores e implementación del código compilado, o para proporcionar atributos de seguridad adicionales específicos del sistema, o para mejorar las capacidades de 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 ejecución; estas extensiones pueden tomar parámetros entre paréntesis después del identificador de atributo del declarador; para la conformidad con ANSI, estas extensiones específicas del compilador deben usar la convención de prefijo de subrayado doble).
- En cualquier otra ubicación, pueden ser identificadores válidos para nuevas declaraciones (y uso posterior si son accesibles).
Constante de puntero nulo
A los efectos de esta sección y solo de esta sección, cada aparición de " 0
" se entiende como "una expresión constante que evalúa 0
, que es de tipo int". En realidad, la expresión constante puede ser de cualquier tipo integral.
Desde los albores de C en 1972, la constante 0
ha tenido el doble papel de constante de puntero entero y nulo. La ambigüedad inherente al doble significado de 0
se trató en C mediante el uso de la macro de preprocesador NULL
, que comúnmente se expande a ((void*)0)
o 0
. C ++ prohíbe la conversión implícita de void *
a otros tipos de punteros, eliminando así el beneficio de convertir 0
a void *
. Como consecuencia, solo 0
se permite como constante de puntero nulo. Esto interactúa mal con la sobrecarga de funciones :
void foo ( char * ); void foo ( int );
Si NULL
se define como 0
(que suele ser el caso en C ++), la declaración foo(NULL);
llamará foo(int)
, lo que es casi seguro que no es lo que pretendía el programador, y no lo que sugiere una lectura superficial del código.
C ++ 11 corrige este mediante la introducción de una nueva palabra clave para servir como una constante puntero nulo distinguen: 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 los tipos integrales, excepto bool
. Si bien la propuesta original especificaba que un rvalue de type nullptr_t
no debería ser convertible a bool
, el grupo de trabajo de lenguaje central decidió que tal conversión sería deseable, para mantener la coherencia con los tipos de punteros regulares. Los cambios de redacción propuestos se votaron por unanimidad en el documento de trabajo en junio de 2008. [2] También se presenta una propuesta similar al grupo de trabajo de la norma C. [15]
Por razones de compatibilidad con versiones anteriores, 0
sigue 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), no a foo (int); / * Tenga en cuenta que foo (nullptr_t) realmente llamará a foo (char *) en el ejemplo anterior usando una conversión implícita, solo si no hay otras funciones sobrecargadas con tipos de punteros compatibles en el alcance. Si existen múltiples sobrecargas, la resolución fallará porque es ambigua, a menos que haya una declaración explícita de foo (nullptr_t). En los encabezados de tipos estándar para C ++ 11, el tipo nullptr_t debe declararse como: typedef decltype (nullptr) nullptr_t; pero no como: typedef int nullptr_t; // versiones anteriores de C ++ que necesitan NULL para definirse como 0 typedef void * nullptr_t; // ANSI C que define NULL como ((void *) 0) * /
Enumeraciones fuertemente tipadas
En C ++ 03, las enumeraciones no son seguras para los tipos. De hecho, son números enteros, incluso cuando los tipos de enumeración son distintos. Esto permite la comparación entre dos valores de enumeración de diferentes tipos de enumeración. La única seguridad que proporciona C ++ 03 es que un número entero o un valor de un tipo de enumeración no se convierte implícitamente en otro tipo de enumeración. Además, el tipo integral subyacente está definido por la implementación; el código que depende del tamaño de la enumeración no es portátil. Por último, los valores de enumeración tienen como ámbito el ámbito adjunto. Por lo tanto, no es posible que dos enumeraciones separadas en el mismo ámbito tengan nombres de miembros coincidentes.
C ++ 11 permite una clasificación especial de enumeración que no tiene ninguno de estos problemas. Esto se expresa mediante la declaración enum class
( enum struct
también se acepta como sinónimo):
enumeración clase Enumeración { Val1 , Val2 , Val3 = 100 , Val4 // = 101 };
Esta enumeración es de tipo seguro. Los valores de la clase de enumeración no se convierten implícitamente en números enteros. Por lo tanto, tampoco se pueden comparar con números enteros (la expresión Enumeration::Val4 == 101
da un error de compilación).
El tipo subyacente de clases de enumeración siempre se conoce. El tipo predeterminado es int
; esto se puede anular a un tipo de integral diferente como se puede ver en este ejemplo:
enum class Enum2 : unsigned int { Val1 , Val2 };
Con las enumeraciones de estilo antiguo, los valores se colocan en el ámbito externo. Con las enumeraciones de estilo nuevo, se colocan dentro del ámbito del nombre de la clase de enumeración. Entonces, en el ejemplo anterior, Val1
no está definido, pero Enum2::Val1
está definido.
También hay 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:
enumeración Enum3 : unsigned long { Val1 = 1 , Val2 };
En este caso, los nombres de los enumeradores se definen en el alcance de la enumeración ( Enum3::Val1
), pero por compatibilidad con versiones anteriores también se colocan en el alcance adjunto.
Las enumeraciones de declaración directa también son posibles en C ++ 11. Anteriormente, los tipos de enumeración no se podían declarar hacia adelante porque el tamaño de la enumeración depende de la definición de sus miembros. Siempre que el tamaño de la enumeración se especifique implícita o explícitamente, se puede declarar hacia adelante:
enum Enum1 ; // No válido en C ++ 03 y C ++ 11; no se puede determinar el tipo subyacente. enumeración 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. enumeración Enum2 : unsigned short ; // No válido en C ++ 11, porque Enum2 se declaró anteriormente con un tipo subyacente diferente.
Soporte en ángulo recto
El analizador sintáctico de C ++ 03 define " >>
" como el operador de desplazamiento correcto o el operador de extracción de flujo en todos los casos. Sin embargo, con las declaraciones de plantilla anidadas, el programador tiende a descuidar 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 de modo que varios corchetes en ángulo recto se interpretarán como un cierre de la lista de argumentos de la plantilla cuando sea razonable. Esto se puede anular usando paréntesis alrededor de las expresiones de los parámetros usando los operadores binarios “ >
”, “ >=
” o “ >>
”:
plantilla < prueba bool > clase SomeType ; std :: vector < SomeType < 1 > 2 >> x1 ; // Interpretado como un std :: vector de SomeType , // seguido de "2 >> x1", que no es una sintaxis válida para un declarador. 1 es cierto. std :: vector < SomeType < ( 1 > 2 ) >> x1 ; // Interpretado como std :: vector de SomeType , // seguido del declarador "x1", que es una sintaxis válida de C ++ 11. (1> 2) es falso.
Operadores de conversión explícitos
C ++ 98 agregó la explicit
palabra clave como un modificador en los constructores para evitar que los constructores de un solo argumento se utilicen como operadores de conversión de tipos implícitos. Sin embargo, esto no hace nada para los operadores de conversión reales. Por ejemplo, una clase de puntero inteligente puede tener un operator bool()
que le permita actuar más como un puntero primitivo: si incluye esta conversión, se puede probar con if (smart_ptr_variable)
(lo 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. Debido a que C ++ bool
se define como un tipo aritmético, se puede convertir implícitamente a tipos integrales o incluso de punto flotante, lo que permite operaciones matemáticas que no están previstas por el usuario.
En C ++ 11, la explicit
palabra clave ahora se puede aplicar a los operadores de conversión. Al igual que con los constructores, evita el uso de esas funciones de conversión en conversiones implícitas. Sin embargo, los contextos de lenguaje que necesitan específicamente un valor booleano (las condiciones de sentencias if y bucles, y operandos para los operadores lógicos) cuentan como conversiones explícitas y, por lo tanto, pueden usar un operador de conversión bool.
Por ejemplo, esta función resuelve limpiamente el problema de bool seguro .
Alias de plantilla
En C ++ 03, es posible definir un typedef solo como sinónimo de otro tipo, incluido 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 < nombre de tipo Primero , nombre de tipo Segundo , int Tercero > class SomeType ;plantilla < nombre de tipo Segunda > typedef SomeType < othertype , Segunda , 5 > TypedefName ; // No válido en C ++ 03
Esto no se compilará.
C ++ 11 agrega esta capacidad con esta sintaxis:
plantilla < nombre de tipo Primero , nombre de tipo Segundo , int Tercero > class SomeType ;plantilla < nombre de tipo Segunda > usando TypedefName = SomeType < othertype , Segunda , 5 > ;
La using
sintaxis también se puede utilizar como alias de tipo en C ++ 11:
typedef void ( * FunctionType ) ( doble ); // Estilo antiguo usando FunctionType = void ( * ) ( double ); // Nueva sintaxis introducida
Uniones sin restricciones
En C ++ 03, existen restricciones sobre qué tipos de objetos pueden ser miembros de un union
. Por ejemplo, las uniones no pueden contener ningún objeto que defina un constructor o destructor no trivial. C ++ 11 elimina algunas de estas restricciones. [3]
Si un union
miembro tiene una función miembro especial no trivial , el compilador no generará la función miembro equivalente para el union
y debe definirse manualmente.
Este es un ejemplo simple de una unión permitida en C ++ 11:
#include // Necesario para la ubicación 'nuevo'.estructura Punto { Punto () {} Punto ( int x , int y ) : x_ ( x ), y_ ( y ) {} int x_ , y_ ; };unión U { int z ; doble w ; Punto p ; // No válido en C ++ 03; válido en C ++ 11. U () {} // Debido al miembro Point, ahora se necesita una definición de constructor. U ( const Point & pt ) : p ( pt ) {} // Construir el objeto Point usando la lista de inicializadores. U & operator = ( const Point & pt ) { new ( & p ) Point ( pt ); devolver * esto ; } // Asignar objeto Point usando la ubicación 'nueva'. };
Los cambios no romperán ningún código existente ya que solo relajan las reglas actuales.
Mejoras en la funcionalidad del lenguaje principal
Estas características permiten que el lenguaje haga cosas que antes eran imposibles, extremadamente detalladas o que necesitaban bibliotecas no portátiles.
Plantillas variadic
En C ++ 11, las plantillas pueden tomar números variables de parámetros de plantilla. Esto también permite la definición de funciones variadas con seguridad de tipos .
Nuevos literales de cadena
C ++ 03 ofrece dos tipos de cadenas literales . La primera clase, contenida entre comillas dobles, produce una matriz de tipo terminada en nulo const char
. El segundo tipo, definido como L""
, produce una matriz de tipo terminada en nulo const wchar_t
, donde wchar_t
es un carácter ancho de tamaño y semántica indefinidos. Ningún tipo de literal ofrece soporte para cadenas literales con UTF-8 , UTF-16 o cualquier otro tipo de codificación Unicode .
La definición del tipo char
se 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 lo suficientemente grande como para contener cualquier miembro del conjunto de caracteres de ejecución básico del compilador. Anteriormente se definía como solo el último en el propio estándar C ++, y luego se basaba en el estándar C para garantizar al menos 8 bits.
C ++ 11 admite tres codificaciones Unicode: UTF-8 , UTF-16 y UTF-32 . Junto con los cambios mencionados anteriormente en la definición de char
, C ++ 11 agrega dos nuevos tipos de caracteres: char16_t
y 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 estas codificaciones se puede hacer 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[]
(tenga en cuenta el prefijo 'u' minúsculo). El tipo de la tercera cadena es const char32_t[]
(prefijo 'U' en mayúsculas).
Al construir literales de cadena Unicode, a menudo es útil insertar puntos de código Unicode directamente en la cadena. Para hacer esto, 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 después de \u
es un número hexadecimal; no necesita el 0x
prefijo habitual . El identificador \u
representa un punto de código Unicode de 16 bits; para ingresar un punto de código de 32 bits, use \U
y un número hexadecimal de 32 bits. Solo se pueden ingresar 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 sustitutos en codificaciones UTF-16.
En ocasiones, también es útil evitar el escape de cadenas de forma manual, especialmente para el uso de literales de archivos XML , lenguajes de secuencias de comandos o expresiones regulares. C ++ 11 proporciona un literal de cadena sin formato:
R "(La cadena de datos \ Cosas") "R "delimitador (The String Data \ Stuff") delimitador "
En el primer caso, todo lo que se encuentra entre "(
y )"
es parte de la cadena. Las "
y \
los personajes no tienen que ser escapado. En el segundo caso, "delimiter(
comienza la cadena y termina solo cuando )delimiter"
se alcanza. La cadena delimiter
puede ser cualquier cadena de hasta 16 caracteres de longitud, incluida la cadena vacía. Esta cadena no puede contener espacios, caracteres de control, (
, )
, o el \
carácter. El uso de esta cadena delimitadora permite al usuario tener )
caracteres dentro de literales de cadena sin formato. Por ejemplo, R"delimiter((a-z))delimiter"
es equivalente a "(a-z)"
. [4]
Los literales de cadena sin formato se pueden combinar con el literal ancho o con cualquiera de los prefijos de literal Unicode:
u8R "XXX (soy una cadena" UTF-8 sin formato ") XXX"uR "* (Esta es una cadena" UTF-16 sin formato ".) *"UR "(Esta es una cadena" UTF-32 sin formato ".)"
Literales definidos por el usuario
C ++ 03 proporciona varios literales. Los caracteres 12.5
son un literal que el compilador resuelve como un tipo double
con el valor de 12,5. Sin embargo, la adición del sufijo f
, como en 12.5f
, crea un valor de tipo float
que contiene el valor 12,5. Los modificadores de sufijo para literales están fijados por la especificación C ++, y el código C ++ 03 no puede crear nuevos modificadores literales.
Por el contrario, C ++ 11 permite al usuario definir nuevos tipos de modificadores literales que construirán objetos basados en la cadena de caracteres que modifica el literal.
La transformación de literales se redefine en dos fases distintas: cruda y cocida. Un literal crudo es una secuencia de caracteres de algún tipo específico, mientras que el literal cocido es de un tipo separado. El C ++ literal 1234
, como un literal prima, es esta secuencia de caracteres '1'
, '2'
, '3'
, '4'
. Como literal cocinado, es el número entero 1234. El C ++ literal 0xA
en forma cruda es '0'
, 'x'
, 'A'
, mientras que en forma cocida es el número entero 10.
Los literales se pueden extender tanto en forma cruda como cocida, con la excepción de los literales de cadena, que solo se pueden procesar en forma cocida. Esta excepción se debe al hecho de que las cadenas tienen prefijos que afectan el significado específico y el tipo 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 subrayado ( _
) están reservados por el estándar. Por lo tanto, todos los literales definidos por el usuario deben tener sufijos que comiencen con un guión bajo ( _
). [dieciséis]
Los literales definidos por el usuario que procesan la forma sin formato del literal se definen mediante un operador literal, que se escribe como operator ""
. A continuación se muestra un ejemplo:
OutputType operator "" _mysuffix ( const char * literal_string ) { // asume que OutputType tiene un constructor que toma un const char * OutputType ret ( literal_string ); return ret ; }OutputType alguna_variable = 1234 _mysuffix ; // asume que OutputType tiene un método get_value () que devuelve un doble assert ( some_variable . Get_value () == 1234.0 )
La declaración de asignación OutputType some_variable = 1234_mysuffix;
ejecuta el código definido por la función literal definida por el usuario. Esta función se pasa "1234"
como una cadena de estilo C, por lo que tiene un terminador nulo.
Un mecanismo alternativo para procesar literales enteros y de punto flotante sin formato es a través de una plantilla variadica :
plantilla < char ... > operador OutputType "" _tuffix (); OutputType alguna_variable = 1234 _tuffix ; OutputType otra_variable = 2.17 _tuffix ;
Esto instancia la función de procesamiento literal como operator "" _tuffix<'1', '2', '3', '4'>()
. En esta forma, no hay ningún carácter nulo que termine la cadena. El propósito principal para hacer esto es usar la constexpr
palabra clave de C ++ 11 para asegurar que el compilador transformará el literal por completo en tiempo de compilación, asumiendo que OutputType
es un tipo constexpr-construible y copiable, y que la función de procesamiento literal es una constexpr
función.
Para literales numéricos, el tipo de literal cocinado es unsigned long long
para literales integrales o long double
para literales de coma flotante. (Nota: No hay necesidad de tipos integrales 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 hay una forma de plantilla alternativa:
Operador OutputType "" _suffix ( unsigned long long ); Operador OutputType "" _suffix ( doble largo ); OutputType some_variable = 1234 _suffix ; // Utiliza la sobrecarga 'unsigned long long'. OutputType otra_variable = 3.1416 _suffix ; // Utiliza la sobrecarga 'doble larga'.
De acuerdo con los nuevos prefijos de cadena mencionados anteriormente, para los literales de cadena, estos se utilizan:
Operador OutputType "" _ssuffix ( const char * string_values , size_t num_chars ); Operador OutputType "" _ssuffix ( const wchar_t * string_values , size_t num_chars ); Operador OutputType "" _ssuffix ( const char16_t * string_values , size_t num_chars ); Operador OutputType "" _ssuffix ( const char32_t * string_values , size_t num_chars ); OutputType some_variable = "1234" _ssuffix ; // Utiliza la sobrecarga 'const char *'. OutputType alguna_variable = u8 "1234" _ssuffix ; // Utiliza la sobrecarga 'const char *'. OutputType some_variable = L "1234" _ssuffix ; // Utiliza la sobrecarga 'const wchar_t *'. OutputType alguna_variable = u "1234" _ssuffix ; // Utiliza la sobrecarga 'const char16_t *'. OutputType alguna_variable = U "1234" _ssuffix ; // Utiliza la sobrecarga 'const char32_t *'.
No existe un formulario de plantilla alternativo. Los literales de caracteres se definen de manera similar.
Modelo de memoria de subprocesos múltiples
C ++ 11 estandariza el soporte para programación multiproceso .
Hay dos partes involucradas: un modelo de memoria que permite que múltiples subprocesos coexistan en un programa y soporte de biblioteca para la interacción entre subprocesos. (Consulte la sección de este artículo sobre funciones de subprocesamiento ).
El modelo de memoria define cuándo varios subprocesos pueden acceder a la misma ubicación de memoria y especifica cuándo las actualizaciones de un subproceso se vuelven visibles para otros subprocesos.
Almacenamiento local de subprocesos
En un entorno de subprocesos múltiples, es común que cada subproceso tenga algunas variables únicas . Esto ya ocurre para las variables locales de una función, pero no sucede para las variables globales y estáticas.
El especificador de almacenamiento indica una nueva duración de almacenamiento local de subprocesos (además de la estática , dinámica y automática existentes ) thread_local
.
Cualquier objeto que pueda tener una duración de almacenamiento estático (es decir, una vida útil que abarque toda la ejecución del programa) puede recibir una duración local de subproceso. La intención es que, como cualquier otra variable de duración estática, un objeto local de subproceso pueda inicializarse usando un constructor y destruirse usando un destructor.
Funciones de miembros especiales explícitamente predeterminadas y eliminadas
En C ++ 03, el compilador proporciona, para las clases que no las 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 anular estos valores predeterminados definiendo versiones personalizadas. C ++ también define varios operadores globales (como operator new
) que funcionan en todas las clases, que el programador puede anular.
Sin embargo, hay muy poco control sobre la creación de estos valores predeterminados. Hacer una clase inherentemente no copiable, por ejemplo, requiere declarar un constructor de copia privado y un operador de asignación de copia y no definirlos. Intentar utilizar estas funciones es una violación de la regla de una definición (ODR). Si bien no se requiere un mensaje de diagnóstico, las violaciones de [17] pueden resultar en un error del enlazador.
En el caso del constructor predeterminado, el compilador no generará un constructor predeterminado si se define una clase con algún constructor. Esto es útil en muchos casos, pero también es útil poder tener tanto constructores especializados como el valor predeterminado generado por el compilador.
C ++ 11 permite la eliminación y eliminación explícita de estas funciones miembro especiales. [18] Por ejemplo, este tipo declara explícitamente que está usando el constructor predeterminado:
struct SomeType { SomeType () = predeterminado ; // El constructor predeterminado se indica explícitamente. SomeType ( othertype valor ); };
Alternativamente, ciertas funciones se pueden deshabilitar explícitamente. Por ejemplo, este tipo no se puede copiar:
struct NonCopyable { NonCopyable () = default ; NonCopiable ( const NonCopyable & ) = eliminar ; NonCopiable & operator = ( const NonCopyable & ) = delete ; };
El = delete
especificador se puede utilizar para prohibir la llamada a cualquier función, que se puede utilizar para no permitir la llamada a una función miembro con parámetros particulares. Por ejemplo:
struct NoInt { void f ( doble i ); void f ( int ) = eliminar ; };
Un intento de llamar f()
con un int
será rechazado por el compilador, en lugar de realizar una conversión silenciosa a double
. Esto se puede generalizar para no permitir llamar a la función con cualquier tipo que no sea el double
siguiente:
struct OnlyDouble { void f ( doble d ); plantilla < clase T > void f ( T ) = eliminar ; };
Tipo long long int
En C ++ 03, el tipo de entero más grande es long int
. Se garantiza tener al menos tantos bits utilizables como int
. Esto resultó en long int
tener un tamaño de 64 bits en algunas implementaciones populares y de 32 bits en otras. C ++ 11 agrega un nuevo tipo de entero long long int
para abordar este problema. Se garantiza que sea al menos tan grande como a long int
y que no tenga 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 admitían como una extensión. [19] [20]
Afirmaciones estáticas
C ++ 03 proporciona dos métodos para probar aserciones : la macro assert
y la directiva de preprocesador #error
. Sin embargo, ninguno de los dos es apropiado para su uso en plantillas: la macro prueba la aserción en el tiempo de ejecución, mientras que la directiva del preprocesador prueba la aserción durante el preprocesamiento, lo que ocurre antes de la instanciación de las plantillas. Ninguno de los dos es apropiado para probar propiedades que dependen de los parámetros de la 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 asume esta forma:
static_assert ( expresión-constante , mensaje-de-error );
A continuación, se muestran algunos ejemplos de cómo static_assert
se puede utilizar:
static_assert (( GREEKPI > 3.14 ) && ( GREEKPI < 3.15 ), "¡GREEKPI es inexacto!" );
template < class T > struct Check { static_assert ( sizeof ( int ) <= sizeof ( T ), "¡T no es lo suficientemente grande!" ); };
plantilla < clase Integral > Integral foo ( Integral x , Integral y ) { static_assert ( std :: is_integral < Integral > :: value , "el parámetro foo () debe ser un tipo integral." ); }
Cuando la expresión constante es, false
el compilador produce un mensaje de error. El primer ejemplo es similar a la directiva de preprocesador #error
, aunque el preprocesador solo admite tipos integrales. [21] Por el contrario, en el segundo ejemplo, la aserción se verifica en cada instanciación de la clase de plantilla Check
.
Las afirmaciones estáticas también son útiles fuera de las plantillas. Por ejemplo, una implementación dada de un algoritmo podría depender del tamaño de un long long
ser más grande que un int
, algo que el estándar no garantiza. Tal suposición es válida en la mayoría de los sistemas y compiladores, pero no en todos.
Permitir sizeof
trabajar en miembros de clases sin un objeto explícito
En C ++ 03, el sizeof
operador se puede utilizar en tipos y objetos. Pero no se puede usar para hacer esto:
struct SomeType { othertype miembro ; };sizeof ( SomeType :: miembro ); // No funciona con C ++ 03. Bien con C ++ 11
Esto debería devolver el tamaño de OtherType
. C ++ 03 no permite esto, por lo que es un error de compilación. C ++ 11 lo permite. También está permitido para el alignof
operador introducido en C ++ 11.
Controlar y consultar la alineación de objetos
C ++ 11 permite consultar y controlar la alineación de variables con alignof
y alignas
.
El alignof
operador toma el tipo y devuelve la potencia del límite de 2 bytes en el que se deben asignar las instancias de tipo (como a std::size_t
). Cuando se le da un tipo de referencia, alignof
devuelve la alineación del tipo al que se hace referencia; para matrices, devuelve la alineación del tipo de elemento.
El alignas
especificador controla la alineación de la memoria para una variable. El especificador toma una constante o un tipo; cuando se proporciona un tipo alignas(T)
es la abreviatura de alignas(alignof(T))
. Por ejemplo, para especificar que una matriz de caracteres debe alinearse correctamente para contener un flotador:
alignas ( float ) unsigned char c [ sizeof ( float )]
Permitir implementaciones de recolección de basura
Los estándares de C ++ anteriores proporcionaban la recolección de basura impulsada por el programador a través de set_new_handler
, pero no proporcionaban una definición de accesibilidad de objetos con el propósito de la recolección de basura automática. C ++ 11 define las condiciones bajo las cuales los valores de puntero se "derivan de forma segura" de otros valores. Una implementación puede especificar que opera bajo estricta seguridad de punteros , en cuyo caso los punteros que no se derivan de acuerdo con estas reglas pueden volverse inválidos.
Atributos
C ++ 11 proporciona una sintaxis estandarizada para extensiones de herramientas / compiladores del lenguaje. Estas extensiones se especificaban tradicionalmente mediante #pragma
directivas o palabras clave específicas del proveedor (como __attribute__
para GNU y __declspec
Microsoft). Con la nueva sintaxis, la información adicional se puede especificar en forma de atributo encerrado entre corchetes dobles. Un atributo se puede aplicar a varios elementos del código fuente:
int [[ attr1 ]] i [[ attr2 , attr3 ]];[[ attr4 ( arg1 , arg2 )]] if ( cond ) { [[ vendedor :: attr5 ]] return i ; }
En el ejemplo anterior, el atributo attr1
está relacionada con el tipo de variable i
, attr2
y attr3
se aplica a la propia variable, attr4
se aplica a la if
declaración y vendor::attr5
se aplica a la instrucción de retorno. En general (pero con algunas excepciones), un atributo especificado para una entidad nombrada se coloca después del nombre, y antes de la entidad, de lo contrario, como se muestra arriba, varios atributos se pueden enumerar 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.
Se recomienda que los atributos no tengan un significado semántico de lenguaje y no cambien el sentido de un programa cuando se ignoran. Los atributos pueden ser útiles para proporcionar información que, por ejemplo, ayude al compilador a emitir mejores diagnósticos u optimizar el código generado.
C ++ 11 proporciona dos atributos estándar en sí mismo: noreturn
para especificar que una función no regresa y carries_dependency
para ayudar a optimizar el código multiproceso indicando que los argumentos de la función o el valor de retorno tienen una dependencia. [ aclaración necesaria ]
Cambios en la biblioteca estándar de C ++
Se introdujeron varias características nuevas en la biblioteca estándar de C ++ 11. Muchos de estos podrían haberse implementado bajo el antiguo estándar, pero algunos se basan (en mayor o menor medida) en las nuevas características principales de C ++ 11.
Una gran parte de las nuevas bibliotecas se definió en el documento C ++ Standards Committee's Library Technical Report (llamado TR1), que se publicó en 2005. Actualmente se encuentran disponibles varias implementaciones completas y parciales de TR1 utilizando el espacio de nombres std::tr1
. Para C ++ 11 se movieron 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 apropiado con características de lenguaje de 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 TR1 original.
Actualizaciones a componentes de 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 actualmente existentes. Por ejemplo, la mayoría de los contenedores de bibliotecas estándar pueden beneficiarse del soporte del constructor de movimientos basado en referencias Rvalue, tanto para mover rápidamente contenedores pesados como para mover el contenido de esos contenedores a nuevas ubicaciones de memoria. Los componentes de la biblioteca estándar se actualizaron con nuevas características del lenguaje C ++ 11 cuando fue apropiado. Estos incluyen, pero no se limitan necesariamente a:
- Referencias de Rvalue y el soporte de movimiento asociado
- Compatibilidad con la unidad de codificación UTF-16 y los tipos de caracteres Unicode de la unidad de codificación UTF-32
- Plantillas variadic (junto con referencias de Rvalue para permitir un reenvío perfecto)
- Expresiones constantes en tiempo de compilación
decltype
explicit
operadores de conversión- Funciones declaradas predeterminadas o eliminadas
Además, ha pasado mucho tiempo desde el estándar C ++ anterior. Se ha escrito mucho código utilizando la biblioteca estándar. Esto ha revelado partes de las bibliotecas estándar que podrían mejorar. Entre las muchas áreas de mejora consideradas se encuentran los asignadores de bibliotecas estándar . Se incluyó un nuevo modelo de asignadores basado en el alcance en C ++ 11 para complementar el modelo anterior.
Instalaciones de roscado
Si bien el lenguaje C ++ 03 proporciona un modelo de memoria que admite subprocesos, el soporte principal para usar realmente subprocesos viene con la biblioteca estándar C ++ 11.
Se proporciona una clase de subproceso ( std::thread
), que toma un objeto de función (y una serie opcional de argumentos para pasarle) para que se ejecute en el nuevo subproceso. Es posible hacer que un subproceso se detenga hasta que se complete otro subproceso en ejecución, proporcionando soporte de unión de subprocesos a través de la std::thread::join()
función miembro. Se proporciona acceso, cuando es posible, a los objetos de subprocesos nativos subyacentes para operaciones específicas de la plataforma por parte de la std::thread::native_handle()
función miembro.
Para la sincronización entre hilos, apropiados mutexes ( std::mutex
, std::recursive_mutex
, etc.) y variables de condición ( std::condition_variable
y std::condition_variable_any
) se añaden a la biblioteca. Se puede acceder a ellos a través de bloqueos ( y ) de adquisición de recursos es inicialización (RAII ) y algoritmos de bloqueo para facilitar su uso.std::lock_guard
std::unique_lock
Para el trabajo de alto rendimiento y bajo nivel, a veces se necesita la comunicación entre subprocesos sin la sobrecarga de las exclusiones mutuas. Esto se hace mediante operaciones atómicas en ubicaciones de memoria. Estos pueden especificar opcionalmente las restricciones mínimas de visibilidad de la memoria necesarias para una operación. Las barreras de memoria explícita también pueden usarse para este propósito.
La biblioteca de subprocesos de C ++ 11 también incluye futuros y promesas para pasar resultados asincrónicos entre subprocesos y std::packaged_task
para finalizar una llamada de función que puede generar un resultado asincrónico. La propuesta de futuros fue criticada porque carece de una forma de combinar futuros y verificar el cumplimiento de una promesa dentro de un conjunto de promesas. [22]
Otras funciones de subprocesamiento de alto nivel, como los grupos de subprocesos, se han remitido a un futuro informe técnico de C ++ . No son parte de C ++ 11, pero se espera que su implementación eventual se construya completamente sobre las características de la biblioteca de subprocesos.
La nueva std::async
instalación proporciona un método conveniente para ejecutar tareas y vincularlas a un std::future
. El usuario puede elegir si la tarea se ejecutará de forma asíncrona en un subproceso independiente o de forma sincrónica en un subproceso que espera el valor. De forma predeterminada, la implementación puede elegir, lo que proporciona una manera fácil de aprovechar la concurrencia de hardware sin exceso de suscripción, y proporciona algunas de las ventajas de un grupo de subprocesos para usos simples.
Tipos de tuplas
Las tuplas son colecciones compuestas por objetos heterogéneos de dimensiones preestablecidas. Una tupla puede considerarse una generalización de las variables miembro de una estructura.
La versión C ++ 11 del tipo de tupla TR1 se benefició de las características de C ++ 11 como las plantillas variadas . Para implementar de manera razonable, la versión TR1 requería un número máximo definido por la implementación de tipos contenidos y un gran truco de macros. Por el contrario, la implementación de la versión C ++ 11 no requiere un número máximo de tipos definido explícitamente por la implementación. Aunque los compiladores tendrán una profundidad de recursividad máxima interna para la instanciación de plantillas (lo cual es normal), la versión C ++ 11 de tuplas no expondrá este valor al usuario.
Usando plantillas variadas , la declaración de la clase tupla se ve de la siguiente manera:
plantilla < clase ... Tipos > clase tupla ;
Un ejemplo de definición y uso del tipo tupla:
typedef std :: tuple < int , double , long & , const char *> test_tuple ; largas largas = 12 ; test_tuple prueba ( 18 , 6.5 , largo , "¡Ciao!" );longy = std :: get < 0 > ( prueba ); // Asignar a 'longy' el valor 18. std :: get < 3 > ( prueba ) = "¡Hermoso!" ; // Modifica el cuarto elemento de la tupla.
Es posible crear la tupla proof
sin 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 tupla: si los tipos de las dos tuplas son iguales, cada tipo de elemento debe poseer un constructor de copia; de lo contrario, cada tipo de elemento de la tupla del lado derecho debe ser convertible al tipo de elemento correspondiente de la tupla del lado izquierdo o que el tipo de elemento correspondiente de la tupla del lado izquierdo tenga un constructor adecuado.
typedef std :: tuple < int , double , string > tuple_1 t1 ; typedef std :: tuple < char , short , const char * > tuple_2 t2 ( 'X' , 2 , "¡Hola!" ); t1 = t2 ; // Ok, los dos primeros elementos se pueden convertir, // el tercero se puede construir a partir de un 'const char *'.
Al igual que std::make_pair
para std::pair
, existe la posibilidad std::make_tuple
de crear automáticamente correos electrónicos std::tuple
utilizando la deducción de tipos y auto
ayuda a declarar dicha tupla. std::tie
crea tuplas de referencias lvalue para ayudar a descomprimir las tuplas. std::ignore
también ayuda aquí. Vea el ejemplo:
registro automático = std :: make_tuple ( "Hari Ram" , "Nueva Delhi" , 3.5 , 'A' ); std :: nombre de la cadena ; flotar gpa ; grado de char ; std :: tie ( nombre , std :: ignorar , gpa , grado ) = registro ; // std :: ignore ayuda a eliminar el nombre del lugar std :: cout << name << '' << gpa << '' << grado << std :: endl ;
Los operadores relacionales están disponibles (entre tuplas con el mismo número de elementos) y dos expresiones están disponibles para verificar las características de una tupla (solo durante la compilación):
std::tuple_size
devuelve el número de elementos de la tupla::value T
,std::tuple_element::type,>
devuelve el tipo de númeroI
de objeto de la tuplaT
.
Tablas hash
Incluir tablas hash (contenedores asociativos desordenados) en la biblioteca estándar de C ++ es una de las solicitudes más recurrentes. No se adoptó en C ++ 03 solo por limitaciones de tiempo. Aunque las tablas hash son menos eficientes que un árbol equilibrado en el peor de los casos (en presencia de muchas colisiones), funcionan mejor en muchas aplicaciones reales.
Las colisiones se gestionan únicamente mediante encadenamiento lineal porque el comité no consideró oportuno estandarizar soluciones de direccionamiento abierto que introducen bastantes 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 "desordenado" en lugar de "hash".
La nueva biblioteca tiene cuatro tipos de tablas hash, diferenciadas por si aceptan o no elementos con la misma clave (claves únicas o claves equivalentes) y si asignan cada clave a un valor asociado. Corresponden a los cuatro contenedores asociativos existentes basados en árboles de búsqueda binaria , con un prefijo unordered_ .
Tipo de tabla hash | Valores asociados | Claves equivalentes |
---|---|---|
std::unordered_set | No | No |
std::unordered_multiset | No | sí |
std::unordered_map | sí | No |
std::unordered_multimap | sí | sí |
Las nuevas clases cumplir con todos los requisitos de una clase de contenedor , y tienen todos los métodos necesarios para elementos de acceso: insert
, erase
, begin
, end
.
Esta nueva característica no necesitaba ninguna extensión del núcleo del lenguaje C ++ (aunque las implementaciones aprovecharán varias características del lenguaje C ++ 11), solo una pequeña extensión del encabezado
y la introducción de encabezados
y
. No se necesitaron otros cambios en ninguna clase estándar existente, y no depende de ninguna otra extensión de la biblioteca estándar.
Expresiones regulares
La nueva biblioteca, definida en el nuevo encabezado
, está formada por un par de clases nuevas:
- las expresiones regulares están representadas por instancia de la clase de plantilla
std::regex
; - las ocurrencias están representadas por instancia de la clase de plantilla
std::match_results
.
La función std::regex_search
se usa para buscar, mientras que para 'buscar y reemplazar' std::regex_replace
se usa la función que devuelve una nueva cadena. Los algoritmos std::regex_search
y std::regex_replace
toman una expresión regular y una cadena y escriben las ocurrencias encontradas en la estructura std::match_results
.
A continuación, se muestra un ejemplo del uso de std::match_results
:
const char * reg_esp = "[,. \\ t \\ n ;:]" ; // Lista de caracteres separadores.// esto se puede hacer usando literales de cadena sin formato: // const char * reg_esp = R "([,. \ t \ n ;:])";std :: regex rgx ( reg_esp ); // 'regex' es una instancia de la clase de plantilla // 'basic_regex' con un argumento de tipo 'char'. std :: cmatch match ; // 'cmatch' es una instancia de la clase de plantilla // 'match_results' con un argumento de tipo 'const char *'. const char * target = "Universidad invisible - Ankh-Morpork" ;// Identifica todas las palabras de 'target' separadas por caracteres de 'reg_esp'. if ( std :: regex_search ( target , match , rgx )) { // Si hay presentes palabras separadas por caracteres especificados. const size_t n = coincidencia . tamaño (); for ( size_t a = 0 ; a < n ; a ++ ) { std :: string str ( coincide con [ a ]. primero , coincide con [ a ]. segundo ); std :: cout << str << " \ n " ; } }
Tenga en cuenta el uso de barras invertidas dobles , porque C ++ usa barra invertida como carácter de escape. La función de cadena sin formato de C ++ 11 podría usarse para evitar el problema.
La biblioteca
no requiere alteración de ningún encabezado existente (aunque los usará cuando sea apropiado) ni una extensión del lenguaje principal. En POSIX C, las expresiones regulares también están disponibles en la biblioteca C POSIX # regex.h .
Punteros inteligentes de uso general
C ++ 11 proporciona std::unique_ptr
y mejoras desde std::shared_ptr
y std::weak_ptr
hacia TR1. std::auto_ptr
es obsoleto.
Función de número aleatorio extensible
La biblioteca estándar de C proporciona la capacidad de generar números pseudoaleatorios a través de la función rand
. Sin embargo, el algoritmo se delega por completo 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 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. Estos dos se combinan para formar un objeto generador de números aleatorios.
A diferencia del estándar C rand
, el mecanismo C ++ 11 vendrá con tres algoritmos de motor de generador base:
linear_congruential_engine
,subtract_with_carry_engine
, ymersenne_twister_engine
.
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_distribution
ypiecewise_linear_distribution
.
El generador y las distribuciones se combinan como en este ejemplo:
#include #include std :: uniform_int_distribution < int > distribución ( 0 , 99 ); std :: motor mt19937 ; // Mersenne twister MT19937 auto generator = std :: bind ( distribución , motor ); int aleatorio = generador (); // Genera una variable integral uniforme entre 0 y 99. int random2 = distribution ( motor ); // Genere otra muestra directamente usando la distribución y los objetos del motor.
Referencia de envoltorio
Se obtiene una referencia de contenedor de una instancia de la clase de plantilla reference_wrapper
. Las referencias de contenedor son similares a las referencias normales (' &
') del lenguaje C ++. Para obtener una referencia contenedora de cualquier objeto ref
se usa la plantilla de función (para una referencia constante cref
se usa).
Las referencias de contenedor son útiles sobre todo para las plantillas de funciones, donde se necesitan referencias a parámetros en lugar de copias:
// Esta función obtendrá una referencia al parámetro 'r' y la incrementará. void func ( int & r ) { r ++ ; }// Función de plantilla. plantilla < clase F , clase P > vacío g ( F f , P t ) { f ( t ); }int main () { int i = 0 ; g ( func , i ); // Se crea una instancia de 'g ' // entonces no se modificará 'i'. std :: cout << i << std :: endl ; // Salida -> 0 g ( función , estándar :: ref ( i )); // Se crea una instancia de 'g >' // luego se modificará 'i'. std :: cout << i << std :: endl ; // Salida -> 1 }
Esta nueva utilidad se agregó al
encabezado existente y no necesitaba más extensiones del lenguaje C ++.
Envoltorios polimórficos para objetos funcionales
Los envoltorios polimórficos para objetos de función son similares a los punteros de función en semántica y sintaxis, pero están menos unidos y pueden hacer referencia indiscriminadamente a cualquier cosa que se pueda llamar (punteros de función, punteros de función miembro o functores) cuyos argumentos son compatibles con los del envoltorio. .
Un ejemplo puede aclarar sus características:
std :: función < int ( int , int ) > func ; // Creación de envoltorios utilizando // la clase de plantilla 'función'. std :: plus < int > add ; // 'más' se declara como 'plantilla T plus (T, T);' // luego 'agregar' es el tipo 'int agregar (int x, int y)'. func = agregar ; // OK: los parámetros y los tipos de retorno son los mismos.int a = func ( 1 , 2 ); // NOTA: si el contenedor 'func' no se refiere a ninguna función, // se lanza la excepción 'std :: bad_function_call'.std :: function < bool ( short , short ) > func2 ; if ( ! func2 ) { // Verdadero porque a 'func2' aún no se le ha asignado una función. bool adyacente ( largo x , largo y ); func2 = & adyacente ; // OK: los parámetros y los tipos de retorno son convertibles. struct Prueba { bool operador () ( corto x , corto y ); }; Coche de prueba ; func = std :: ref ( coche ); // 'std :: ref' es una función de plantilla que devuelve el contenedor // de la función miembro 'operator ()' de la estructura 'car'. } func = func2 ; // OK: los parámetros y los tipos de retorno son convertibles.
La clase de plantilla function
se definió dentro del encabezado
, sin necesidad de ningún cambio en el lenguaje C ++.
Tipo de rasgos para la metaprogramación
La metaprogramación consiste en crear un programa que crea o modifica otro programa (o él mismo). Esto puede suceder durante la compilación o durante la ejecución. El Comité de Estándares de C ++ ha decidido introducir una biblioteca que permite la metaprogramación durante la compilación mediante plantillas.
A continuación se muestra un ejemplo de un metaprograma que utiliza el estándar C ++ 03: una recursividad de instancias de plantilla para calcular exponentes enteros:
template < int B , int N > struct Pow { // llamada recursiva y recombinación. enum { valor = B * Pow < B , N -1 > :: valor }; };template < int B > struct Pow < B , 0 > { // '' N == 0 '' condición de terminación. enum { valor = 1 }; }; int quartic_of_three = Pow < 3 , 4 > :: valor ;
Muchos algoritmos pueden operar con diferentes tipos de datos; Las plantillas de C ++ admiten 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 de plantilla utilizando 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). Están definidos en el nuevo encabezado
.
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.do_it
).
// Primera forma de operar. plantilla < bool B > struct Algorithm { plantilla < clase T1 , clase T2 > static int do_it ( T1 & , T2 & ) { /*...*/ } };// Segunda forma de operar. plantilla <> struct Algoritmo < verdadero > { plantilla < clase T1 , clase T2 > static int do_it ( T1 , T2 ) { /*...*/ } };// La creación de instancias de 'elaborado' instanciará automáticamente la forma correcta de operar. template < class T1 , class T2 > int elaborate ( T1 A , T2 B ) { // Use la segunda forma solo si 'T1' es un número entero y si 'T2' está // en coma flotante, de lo contrario use la primera forma. return Algoritmo < std :: is_integral < T1 > :: value && std :: is_floating_point < T2 > :: value > :: do_it ( A , B ) ; }
A través de los rasgos de tipo , definidos en el encabezado
, también es posible crear operaciones de transformación de tipo ( static_cast
y const_cast
son insuficientes dentro de una plantilla).
Este tipo de programación produce un código elegante y conciso; sin embargo, el punto débil de estas técnicas es la depuración: incómoda durante la compilación y muy difícil durante la ejecución del programa.
Método uniforme para calcular el tipo de retorno de objetos de función
Determinar el tipo de retorno de un objeto de función de plantilla en tiempo de compilación no es intuitivo, particularmente si el valor de retorno depende de los parámetros de la función. Como ejemplo:
struct Clear { int operator () ( int ) const ; // El tipo de parámetro es double operator () ( double ) const ; // igual al tipo de retorno. };plantilla < clase Obj > clase Cálculo { público : plantilla < clase Arg > operador Arg () ( Arg & a ) const { miembro de retorno ( a ); } privado : miembro de Obj ; };
Al crear una instancia de la plantilla de clase Calculus
, el objeto de función de calculus
siempre tendrá el mismo tipo de retorno que el objeto de función de Clear
. Sin embargo, dada la clase a Confused
continuación:
struct Confused { operador doble () ( int ) const ; // El tipo de parámetro no es int operator () ( double ) const ; // igual al tipo de retorno. };
Intentar crear Calculus
una instancia hará que el tipo de retorno de Calculus
no sea el mismo que el de class Confused
. El compilador puede generar advertencias sobre la conversión de int
a double
y viceversa.
TR1 introduce, y C ++ 11 adopta, la clase de plantilla std::result_of
que permite determinar y utilizar el tipo de retorno de un objeto de función para cada declaración. El objeto CalculusVer2
usa el std::result_of
objeto para derivar el tipo de retorno del objeto de función:
template < class Obj > class CalculusVer2 { public : template < class Arg > typename std :: result_of < Obj ( Arg ) > :: type operator () ( Arg & a ) const { return member ( a ); } privado : miembro de Obj ; };
De esta forma, en las instancias del objeto de función CalculusVer2
no hay conversiones, advertencias o errores.
El único cambio con respecto a la versión TR1 de std::result_of
es que la versión TR1 permitió que una implementación fallara para poder determinar el tipo de resultado de una llamada de función. Debido a cambios en C ++ para el soporte decltype
, la versión C ++ 11 de std::result_of
ya no necesita estos casos especiales; se requieren implementaciones para calcular un tipo en todos los casos.
Compatibilidad mejorada con C
Por compatibilidad con C , desde C99, se agregaron estos: [23]
- Preprocesador: [24]
- macros variadic ,
- 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 evalúa el 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
Rumbo a un TR separado:
- Módulos
- Tipos decimales
- Funciones especiales matemáticas
Aplazado:
- Conceptos
- Soporte de recolección de basura más completo o requerido
- Reflexión
- Ámbitos macro
Funciones eliminadas o obsoletas
El término punto de secuencia se eliminó y se reemplazó especificando que una operación está secuenciada antes que otra o que dos operaciones no están secuenciadas. [25]
Se export
eliminó el uso anterior de la palabra clave . [26] La palabra clave en sí permanece, reservada para un posible uso futuro.
Las especificaciones de excepción dinámica están en desuso. [26] La especificación en tiempo de compilación de funciones que no generan excepciones está disponible con la noexcept
palabra clave, que es útil para la optimización.
std::auto_ptr
está en desuso, 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 en desuso.
Ver también
- C11
Referencias
- ^ "Tenemos un estándar internacional: C ++ 0x está aprobado por unanimidad" . Consultado el 12 de agosto de 2011 .
- ^ Sutter, Herb (18 de agosto de 2014), ¡ Tenemos C ++ 14! , consultado el 18 de agosto de 2014
- ^ Stroustrup, Bjarne. "Preguntas frecuentes de C ++ 11" . stroustrup.com .
- ^ "Descripción general de C ++ 11: ¿Qué objetivos de diseño específicos guiaron al comité?" . C ++ estándar .
- ^ "Bjarne Stroustrup: una descripción general de C ++ 0x" (PDF) . Consultado el 30 de junio de 2011 .
- ^ "ISO / IEC 14882: 2011" . YO ASI. 2 de septiembre de 2011 . Consultado el 3 de septiembre de 2011 .
- ^ "Borrador de trabajo, estándar para lenguaje de programación C ++" (PDF) .
- ^ "El Estándar" . Consultado el 2 de noviembre de 2012 .
- ^ 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) .
- ^ Jaakko Järvi; Bjarne Stroustrup; Douglas Gregor; Jeremy Siek (28 de abril de 2003). "Decltype y auto, Lenguaje de programación C ++, Documento no: N1478 = 03-0061" (PDF) .
- ^ Roger Orr (junio de 2013). " " Auto - ¿Un mal necesario? "Diario de sobrecarga # 115" .
- ^ "Documento no: N1968 = 06-0038- Expresiones lambda y cierres para C ++" (PDF) . Estándares abiertos.
- ^ "especificador automático (desde C ++ 11) - cppreference.com" . en.cppreference.com .
- ^ Gustedt, Jens (9 de julio de 2019). "Introduzca la constante nullptr - v1" (PDF) . Registro de documentos ISO JTC1 / SC22 / WG14 . Organización Internacional de Normalización: a través de open-std.org.
- ^ Esto provocó un conflicto con el uso propuesto (común en otros idiomas) del guión bajo para la agrupación de dígitos en literales numéricos como literales enteros , por lo que C ++ 14 en su lugar usa el apóstrofo (como una coma superior ) para agrupar. Daveed Vandevoorde (21 de septiembre de 2012). "N3448: separación indolora de dígitos" (PDF) ., Lawrence Crowl (19 de diciembre de 2012). "N3499: Separadores de dígitos" .
- ^ ISO / IEC (2003). ISO / IEC 14882 : 2003 (E): Lenguajes de programación - C ++ §3.2 Una regla de definición [basic.def.odr] párr. 3
- ^ "Funciones predeterminadas y eliminadas - ISO / IEC JTC1 SC22 WG21 N2210 = 07-0070 - 2007-03-11" .
- ^ "Uso de la colección de compiladores GNU (GCC): Long Long" . gcc.gnu.org .
- ^ Rangos de tipo de datos (C ++)
- ^ Samuel P. Harbison III, Guy L. Steele Jr .: "C - Un manual de referencia", quinta edición, p.251
- ^ Milewski, Bartosz (3 de marzo de 2009). "Futuros de promesas rotas-C ++ 0x" . Consultado el 24 de enero de 2010 .
- ^ "Clang: estado de C ++ 98, C ++ 11 y C ++ 14" . Clang.llvm.org. 2013-05-12 . Consultado el 10 de junio de 2013 .
- ^ "Borrador de trabajo de cambios para la sincronización del preprocesador C99" . www.open-std.org .
- ^ Caves, Jonathan (4 de junio de 2007). "Actualización sobre el estándar de lenguaje C ++ - 0x" . Consultado el 25 de mayo de 2010 .
- ^ a b Sutter, Herb (3 de marzo de 2010). "Informe de viaje: reunión de normas ISO C ++ de marzo de 2010" . Consultado el 24 de marzo de 2010 .
enlaces externos
- El Comité de Estándares de C ++
- C ++ 0X: la nueva cara de C ++ estándar
- Cobertura del blog de Herb Sutter sobre C ++ 11
- Cobertura del blog de Anthony Williams sobre C ++ 11
- Una charla sobre C ++ 0x impartida por Bjarne Stroustrup en la Universidad de Waterloo
- El estado de la lengua: una entrevista con Bjarne Stroustrup (15 de agosto de 2008)
- Página wiki para ayudar a realizar un seguimiento de las características del lenguaje principal de C ++ 0x y su disponibilidad en los compiladores
- Referencia de la biblioteca estándar de C ++ 11 en línea
- Compilador de C ++ 11 en línea
- Preguntas frecuentes sobre C ++ 11 de Bjarne Stroustrup
- Más información sobre las características de C ++ 11: bucle for basado en rango, por qué auto_ptr está en desuso, etc.