C++11 es una versión de un estándar técnico conjunto , ISO/IEC 14882, de la Organización Internacional de Normalización (ISO) y la Comisión Electrotécnica Internacional (IEC), para el lenguaje de programación C++ . C++11 reemplazó la versión anterior del estándar C++, denominada C++03 , [1] y luego fue reemplazada por C++14 . El nombre sigue la tradición de nombrar las versiones del lenguaje por 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. [2]
Aunque uno de los objetivos de diseño era preferir los cambios en las bibliotecas a los cambios en el lenguaje central , [3] C++11 hace varias adiciones al lenguaje central. Las áreas del lenguaje central que se mejoraron significativamente incluyen soporte multihilo, soporte de programación genérica , inicialización uniforme y rendimiento. También se realizaron cambios significativos en la Biblioteca estándar de C++ , incorporando la mayoría de las bibliotecas del Informe técnico 1 (TR1) de C++ , excepto la biblioteca de funciones matemáticas especiales. [4]
C++11 se publicó como ISO/IEC 14882:2011 [5] en septiembre de 2011 y está disponible a cambio de una tarifa. El borrador de trabajo más similar al estándar C++11 publicado es el N3337, con fecha del 16 de enero de 2012; [6] solo tiene correcciones editoriales con respecto al estándar C++11. [7]
C++11 es totalmente compatible con Clang 3.3 y versiones posteriores. [8] C++11 es totalmente compatible con GNU Compiler Collection (GCC) 4.8.1 y versiones posteriores. [9]
El comité de diseño intentó cumplir una serie de objetivos al diseñar C++11:
Se considera importante la atención a los principiantes, porque la mayoría de los programadores informáticos siempre lo serán, y porque muchos principiantes nunca amplían sus conocimientos, limitándose a trabajar en aspectos del lenguaje en el que se especializan. [2] [3]
Una de las funciones del comité de C++ es el desarrollo del núcleo del lenguaje. Las áreas del núcleo del lenguaje que se mejoraron significativamente incluyen la compatibilidad con subprocesos múltiples , la compatibilidad con programación genérica , la inicialización uniforme y el rendimiento.
Estas características del lenguaje existen principalmente para proporcionar algún tipo de beneficio en el rendimiento en tiempo de ejecución , ya sea de memoria o de velocidad de procesamiento. [ cita requerida ]
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 no ser modificables nunca, al igual que en C, y se consideraba que no se podían distinguir de const T&
los tipos; sin embargo, en algunos casos, los temporales podrían haberse modificado, un comportamiento que incluso se consideró una laguna útil. [10] C++11 agrega un nuevo tipo de referencia no constante llamadoReferencia de valor r , identificada porT&&
. Esto hace referencia a los valores temporales que pueden modificarse después de inicializarse, con el fin de permitir la "semántica de movimiento".
Un problema crónico de rendimiento con C++03 son las copias profundas innecesarias y costosas que pueden ocurrir implícitamente cuando los objetos se pasan por valor. Para ilustrar el problema, considere que un std::vector<T>
es, internamente, un contenedor alrededor de una matriz de estilo C con un tamaño definido. Si std::vector<T>
se crea o devuelve un objeto temporal desde una función, solo se puede almacenar creando un nuevo objeto std::vector<T>
y copiando todos los datos del valor r en él. Luego, se destruye el objeto temporal y toda su memoria. (Para simplificar, esta discusión ignora la optimización del valor de retorno ).
En C++11, un constructor de movimiento std::vector<T>
que toma una referencia rvalue a un std::vector<T>
puede copiar el puntero a la matriz interna de estilo C desde el rvalue al new std::vector<T>
y luego establecer el puntero dentro del rvalue como null. Dado que el temporal nunca se volverá a utilizar, ningún código intentará acceder al puntero null y, debido a que el puntero es null, su memoria no se elimina cuando sale del ámbito. Por lo tanto, la operación no solo evita el gasto de una copia profunda, sino que es segura e invisible.
Las referencias a valores r pueden proporcionar ventajas de rendimiento al código existente sin necesidad de realizar ningún cambio fuera de la biblioteca estándar. std::vector<T>
No es necesario cambiar explícitamente el tipo del valor devuelto de una función que devuelve un valor temporal std::vector<T> &&
para invocar el constructor de movimiento, ya que los valores temporales se consideran valores r automáticamente. (Sin embargo, si std::vector<T>
es una versión de C++03 sin un constructor de movimiento, entonces el constructor de copia se invocará con un const std::vector<T>&
, lo que implica una asignación de memoria significativa).
Por razones de seguridad, se imponen algunas restricciones. Una variable nombrada nunca se considerará un valor r, incluso si se declara como tal. Para obtener un valor r, std::move()
se debe utilizar la plantilla de función. Las referencias a valores r también se pueden modificar solo en determinadas circunstancias, ya que están pensadas para usarse principalmente con constructores de movimiento.
Debido a la naturaleza de la redacción de las referencias rvalue y a algunas modificaciones en la redacción de las referencias lvalue (referencias regulares), las referencias rvalue permiten a los desarrolladores proporcionar un reenvío de funciones perfecto. Cuando se combina con plantillas variádicas, esta capacidad permite que las plantillas de funciones puedan reenviar argumentos perfectamente a otra función que tome esos argumentos particulares. Esto es muy útil para reenviar parámetros de constructor, para crear funciones de fábrica que llamarán automáticamente al constructor correcto para esos argumentos particulares. Esto se ve en el conjunto emplace_back de los métodos de la biblioteca estándar de C++.
C++ siempre ha tenido el concepto de expresiones constantes. Se trata de expresiones que 3+4
siempre arrojarán los mismos resultados, tanto en tiempo de compilación como en tiempo de ejecución. Las expresiones constantes son oportunidades de optimización para los compiladores, que con frecuencia las ejecutan en tiempo de compilación y codifican los resultados en el programa. Además, en varios lugares, la especificación de C++ requiere el uso de expresiones constantes. Para definir una matriz se requiere una expresión constante y los valores de enumeración deben ser expresiones constantes.
Sin embargo, nunca se ha permitido que una expresión constante contenga una llamada a una función o un constructor de objetos. Por lo tanto, un fragmento de código tan simple como este no es válido:
int obtener_cinco () { devolver 5 ;} int some_value [ get_five () + 7 ]; // Crea una matriz de 12 números 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 a 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 un constructor de función o de objeto sea una constante de tiempo de compilación. [11] El ejemplo anterior se puede reescribir de la siguiente manera:
constexpr int obtener_cinco () { devolver 5 ;} int some_value [ get_five () + 7 ]; // Crea una matriz de 12 números enteros. Válido en C++11
Esto permite al compilador comprender y verificar que get_five()
es una constante de tiempo de compilación.
El uso constexpr
de una función impone algunos límites a lo que esa función puede hacer. En primer lugar, la función debe tener un tipo de retorno que no sea void. 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 null y una única declaración de retorno. Deben existir valores de argumentos 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 se podían utilizar en expresiones constantes solo si las variables se declaraban como constantes, tenían un inicializador que era una expresión constante y eran de tipo integral o de enumeración. C++11 elimina la restricción de que las variables debían ser de tipo integral o de enumeración si se definen con la constexpr
palabra clave:
constexpr doble aceleración gravitacional de la tierra = 9.8 ; constexpr doble aceleración gravitacional de la luna = aceleración gravitacional de la tierra / 6.0 ;
Estas 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, también se pueden declarar constructores con constexpr
. constexpr
El cuerpo de la función de un constructor solo puede contener declaraciones y sentencias nulas, y no puede declarar variables ni definir tipos, como en el caso de una constexpr
función. Deben existir valores de argumentos de modo que, después de la sustitución de argumentos, inicialice los miembros de la clase con expresiones constantes. Los destructores para dichos tipos deben ser triviales.
El constructor de copia para un tipo con cualquier constexpr
constructor normalmente también debe definirse como un constexpr
constructor, para permitir que los objetos del tipo se devuelvan por valor desde una función constexpr. Cualquier función miembro de una clase, como los constructores de copia, las sobrecargas de operadores, etc., se pueden declarar como constexpr
, siempre que cumplan con los requisitos para las funciones constexpr. Esto permite que el compilador copie objetos en tiempo de compilación, realice operaciones sobre 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
se diferencia de consteval
, introducido en C++20 , en que este último siempre debe producir una constante de tiempo de compilación, mientras que constexpr
no tiene esta restricción.
En C++03, una clase o estructura debe seguir una serie de reglas para que se la considere un tipo de datos simple (POD). Los tipos que se ajustan a esta definición producen diseños de objetos que son compatibles con C y también se pueden inicializar estáticamente. El estándar C++03 tiene restricciones sobre qué tipos son compatibles con C o se pueden inicializar estáticamente a pesar de que no haya ninguna razón técnica por la que un compilador no pueda aceptar el programa; si alguien creara un tipo POD de C++03 y añadiera 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 se hiciera ningún cambio 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: trivial y diseño estándar .
Un tipo trivial se puede inicializar de forma estática. Esto también significa que es válido copiar datos mediante memcpy
, en lugar de tener que usar un constructor de copia. La duración 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:
SomeConstructor() = default;
).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 con diseño estándar significa que ordena y empaqueta sus miembros de una manera que es compatible con C. Una clase o estructura tiene un diseño estándar, por definición, siempre que:
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 de copia y movimiento complejos puede no ser trivial, pero podría tener un diseño estándar y, por lo tanto, interoperar con C. De manera similar, una clase con miembros de datos públicos y privados no estáticos no tendría un diseño estándar, pero podría ser trivial y, por lo tanto, memcpy
interoperable.
En C++03, el compilador debe crear una instancia de una plantilla cada vez que se encuentra una plantilla completamente especificada en una unidad de traducción. Si la plantilla se crea 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 externos.
C++03 tiene esta sintaxis para obligar al compilador a instanciar una plantilla:
plantilla clase std :: vector < MyClass > ;
C++11 ahora proporciona esta sintaxis:
clase de plantilla externa std :: vector < MyClass > ;
que le dice al compilador que no cree una instancia de la plantilla en esta unidad de traducción.
Estas características existen con el objetivo principal de hacer que el lenguaje sea más fácil de usar. Pueden mejorar la seguridad de tipos, minimizar la repetición de código, hacer que sea menos probable que se produzcan códigos erróneos, etc.
C++03 heredó la característica de lista de inicializadores de C. A una estructura o matriz se le proporciona 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 una estructura que contenga otras estructuras pueden usarlas.
struct Objeto { float primero ; int segundo ; }; Objeto escalar = { 0.43f , 10 }; //Un objeto, con primero=0.43f y segundo=10 Objeto 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 ajusten 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, denominada std::initializer_list
. Esto permite que los constructores y otras funciones tomen listas de inicializadores como parámetros. Por ejemplo:
clase SequenceClass { público : SequenceClass ( std :: initializer_list < int > lista ); };
Esto permite SequenceClass
construir a partir de una secuencia de números enteros, como por ejemplo:
SequenceClass alguna_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 reciben un tratamiento especial durante la inicialización uniforme (ver a continuación).
La clase de plantilla std::initializer_list<>
es un tipo de biblioteca estándar de C++11 de primera clase . El compilador de C++11 puede construirlas estáticamente mediante el uso de la {}
sintaxis sin un nombre de tipo en contextos donde dichas llaves deducirán un std::initializer_list
, o especificando explícitamente el tipo como std::initializer_list<SomeType>{args}
(y así sucesivamente para otras variedades de sintaxis de construcción).
La lista se puede copiar una vez construida, lo que es barato y funcionará como una copia por referencia (la clase se implementa normalmente como un par de punteros de inicio/fin). Una std::initializer_list
es constante: sus miembros no se pueden cambiar una vez que se crea, y tampoco se pueden cambiar los datos en esos miembros (lo que descarta el movimiento desde ellos, la necesidad de 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 en los constructores de clases. Las funciones regulares pueden tomar std::initializer_list
s tipificadas como argumentos. Por ejemplo:
void function_name ( std :: initializer_list < float > list ); // Copiar es barato; ver arriba nombre_función ({ 1.0f , -3.45f , -0.4f });
Ejemplos de esto en la biblioteca estándar incluyen las plantillas std::min()
y que toman s de tipo numérico.std::max()
std::initializer_list
Los contenedores estándar también se pueden inicializar de estas maneras:
std :: vector < std :: string > v = { "xyzzy" , "plugh" , "abracadabra" }; std :: vector < std :: string > v ({ "xyzzy" , "plugh" , "abracadabra" }); std :: vector < std :: string > v { "xyzzy" , "plugh" , "abracadabra" }; // vea "Inicialización uniforme" a continuación
C++03 tiene varios problemas con la inicialización de tipos. Existen varias formas de hacerlo y algunas producen resultados diferentes cuando se intercambian. La sintaxis del constructor tradicional, por ejemplo, puede parecer una declaración de función y se deben tomar medidas para garantizar que la regla de análisis más complicada del compilador no la confunda con tal. Solo los agregados y los tipos POD se pueden inicializar con inicializadores de agregados (usando SomeType var = {/*stuff*/};
).
C++11 proporciona una sintaxis que permite una inicialización de tipos totalmente uniforme que funciona en cualquier objeto. Amplía la sintaxis de la lista de inicializadores:
estructura BasicStruct { int x ; doble y ; }; estructura AltStruct { AltStruct ( int x , doble y ) : x_ { x } , y_ { y } {} privado : int x_ ; doble y_ ; }; Estructura básica var1 { 5 , 3.2 }; Estructura alternativa var2 { 2 , 4.3 };
La inicialización de var1
se 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. Se utilizará la conversión de tipo implícita cuando sea necesario. Si no existe ninguna conversión, o solo existe una conversión de restricción, el programa está mal formado. La inicialización de var2
invoca al constructor.
También puedes hacer esto:
struct IdString { std :: string nombre ; int identificador ; }; 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 aún es necesaria en ocasiones. Si una clase tiene un constructor de lista de inicializadores ( TypeName(initializer_list<SomeType>);
), 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 > el_vec { 4 };
llamará al constructor de la lista de inicializadores, no al constructor de std::vector
que toma un único parámetro de tamaño y crea el vector con ese tamaño. Para acceder a este último constructor, el usuario deberá utilizar directamente la sintaxis estándar del constructor.
En C++03 (y C), para utilizar una variable, su tipo debe especificarse explícitamente. Sin embargo, con la llegada de los tipos de plantilla y las técnicas de metaprogramación de plantillas, el tipo de algo, en particular el valor de retorno bien definido de una función, puede no ser fácil de expresar. Por lo tanto, almacenar intermediarios en variables es difícil y posiblemente se necesite conocer 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. [12] [13] Esto crea una variable del tipo específico del inicializador:
auto algún_tipo_extraño_invocable = std :: bind ( & alguna_función , _2 , _1 , algún_objeto ); auto otra_variable = 5 ;
El tipo de some_strange_callable_type
es simplemente lo que la función de plantilla particular reemplaza std::bind
para esos argumentos particulares. Este tipo se determina fácilmente de manera procedimental por el compilador como parte de sus tareas de análisis semántico, pero no es fácil para el usuario determinarlo mediante inspección. El tipo de other_variable
también está bien definido, pero es más fácil para el usuario determinarlo. Es un int
, que es el mismo tipo que el literal entero.
Este uso de la palabra clave auto
en C++ reutiliza la semántica de esta palabra clave, que originalmente se usaba en el lenguaje predecesor sin tipo B en una función relacionada 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 algún_int ; decltype ( algún_int ) otra_variable_entera = 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 utilizar 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 ( const auto & x : myvec )
Esta diferencia aumenta a medida que el programador comienza a anidar contenedores, aunque en tales casos typedef
los s son una buena manera de disminuir la cantidad de código.
El tipo denotado por decltype
puede ser diferente del tipo deducido por auto
.
#include <vector> int main () { const std :: vector < int > v ( 1 ); auto a = v [ 0 ]; // a tiene tipo int decltype ( v [ 0 ]) b = 1 ; // b tiene tipo const int&, el tipo de retorno de // std::vector<int>::operator[](size_type) const auto c = 0 ; // c tiene tipo int auto d = c ; // d tiene tipo int decltype ( c ) e ; // e tiene tipo int, el tipo de la entidad nombrada por c decltype (( c )) f = c ; // f tiene tipo int&, porque (c) es un lvalue decltype ( 0 ) g ; // g tiene tipo int, porque 0 es un rvalue }
C++11 extiende la sintaxis de la for
declaración para permitir una fácil iteración sobre un rango 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 tipos para elementos de matriz para ( auto & x : my_array ) x *= 2 ;
Esta forma de for
, llamada "for basada en rango", iterará sobre cada elemento de la lista. Funcionará para matrices de estilo C, listas de inicializadores y cualquier tipo que tenga begin()
funciones end()
definidas para él que devuelvan iteradores. Todos los contenedores de la biblioteca estándar que tienen pares de inicio/fin funcionarán con la declaración for basada en rango.
C++11 proporciona la capacidad de crear funciones anónimas , llamadas funciones lambda. [14] Estas se definen de la siguiente manera:
[]( int x , int y ) -> int { devolver x + y ; }
El tipo de retorno ( -> int
en este ejemplo) se puede omitir siempre que todas return
las expresiones devuelvan el mismo tipo. Una expresión lambda puede ser opcionalmente un cierre .
La sintaxis de declaración de funciones estándar de C era perfectamente adecuada para el conjunto de características del lenguaje C. A medida que C++ evolucionó a partir de C, mantuvo la sintaxis básica y la amplió cuando fue necesario. Sin embargo, a medida que C++ se volvió más complejo, expuso varias limitaciones, especialmente en lo que respecta a las declaraciones de funciones de plantilla. Por ejemplo, en C++03 esto no es válido:
plantilla < class Lhs , class Rhs > Ret agregando_func ( const Lhs & lhs , const Rhs & rhs ) { return lhs + rhs ;} //Ret debe ser del tipo lhs+rhs
El tipo es el que se obtiene con Ret
la suma de tipos . Incluso con la funcionalidad de C++11 mencionada anteriormente , esto no es posible:Lhs
Rhs
decltype
plantilla < clase Lhs , clase Rhs > decltype ( lhs + rhs ) agregando_func ( const Lhs & lhs , const Rhs & rhs ) { return lhs + rhs ;} //No válido C++11
Esto no es C++ válido porque lhs
y rhs
aún no se han definido; no serán identificadores válidos hasta que el analizador haya analizado el resto del prototipo de función.
Para solucionar este problema, C++11 introdujo una nueva sintaxis de declaración de función, con un tipo de retorno final : [15]
plantilla < clase Lhs , clase Rhs > función_de_adición_automática ( const Lhs & lhs , const Rhs & rhs ) -> decltype ( lhs + rhs ) { return lhs + rhs ; }
Esta sintaxis se puede utilizar para declaraciones y definiciones de funciones más mundanas:
estructura SomeStruct { auto nombre_función ( int x , int y ) -> int ; }; auto SomeStruct :: nombre_función ( int x , int y ) -> int { return x + y ; }
El uso de la palabra clave “auto” en este caso es solo parte de la sintaxis y no realiza una deducción automática de tipo en C++11. Sin embargo, a partir de C++14, el tipo de retorno final se puede eliminar por completo y el compilador deducirá el tipo de retorno automáticamente. [16]
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 los miembros de su clase por sí mismo o llamar a una función miembro común, de la siguiente manera:
clase SomeType { público : SomeType ( int nuevo_número ) { Construir ( nuevo_número ); } SomeType () { Construir ( 42 ); } privado : void Construct ( int nuevo_numero ) { numero = nuevo_numero ; } int numero ; };
Los constructores de clases base no se pueden exponer directamente a clases derivadas; cada clase derivada debe implementar constructores incluso si un constructor de clase base fuera apropiado. Los miembros de datos no constantes de 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 que los constructores llamen a otros constructores pares (denominado delegación ). Esto permite que los constructores utilicen 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 .
La sintaxis es la siguiente:
clase SomeType { int numero ; público : AlgúnTipo ( int nuevo_número ) : número ( nuevo_número ) {} AlgúnTipo () : AlgúnTipo ( 42 ) {} };
En este caso, se podría haber logrado el mismo efecto creando new_number
un parámetro predeterminado. Sin embargo, la nueva sintaxis permite que el valor predeterminado (42) se exprese en la implementación en lugar de en la interfaz, lo que es un beneficio para los encargados del mantenimiento del código de la biblioteca, ya que los valores predeterminados para los parámetros de función están "integrados" en los sitios de llamada, mientras que la delegación del constructor permite cambiar el valor sin tener que volver a compilar el código utilizando la biblioteca.
Esto tiene una salvedad: C++03 considera que un objeto se construye cuando su constructor termina de ejecutarse, pero C++11 considera que un objeto se construye una vez que cualquier constructor termina de ejecutarse. Dado que se permitirá la ejecución de varios constructores, esto significará que cada constructor delegado 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.
En el caso de los constructores de clase base, C++11 permite que una clase especifique que los constructores de clase base se heredarán. Por lo tanto, el compilador de C++11 generará código para realizar la herencia y el reenvío de la clase derivada a la clase base. Esta es una característica de todo o nada: o se reenvían todos los constructores de esa clase base o no se reenvía ninguno. Además, un constructor heredado se ocultará si coincide con la firma de un constructor de la clase derivada, y existen restricciones para la herencia múltiple: los constructores de clase no se pueden heredar de dos clases que utilicen constructores con la misma firma .
La sintaxis es la siguiente:
clase BaseClass { público : BaseClass ( int valor ); }; clase DerivedClass : pública BaseClass { pública : usando BaseClass :: BaseClass ; };
Para la inicialización de miembros, C++11 permite esta sintaxis:
clase SomeClass { público : SomeClass () {} explícito SomeClass ( int nuevo_valor ) : valor ( nuevo_valor ) {} privado : int valor = 5 ; };
Cualquier constructor de la clase se inicializará value
con 5, si el constructor no reemplaza la inicialización con la suya propia. Por lo tanto, 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 utilizar el constructor o la inicialización uniforme, en lugar de la inicialización de asignación que se muestra arriba.
En C++03, es posible crear accidentalmente una nueva función virtual cuando se desea anular una función de la clase base. Por ejemplo:
estructura Base { virtual void alguna_función ( float ); }; estructura Derivada : Base { virtual void alguna_función ( int ); };
Supongamos que Derived::some_func
se pretende reemplazar la versión de la clase base, pero en lugar de ello, como tiene una firma diferente , crea una segunda función virtual. Este es un problema común, en particular cuando un usuario intenta modificar la clase base.
C++11 proporciona una sintaxis para resolver este problema.
estructura Base { virtual void alguna_función ( 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 comprobará la(s) clase(s) base para ver si existe una función virtual con esta firma exacta. Y si no la hay, el compilador indicará un error.
C++11 también agrega la capacidad de evitar la herencia de clases o simplemente evitar la anulación de métodos en clases derivadas. Esto se hace con el identificador especial final
. Por ejemplo:
estructura Base1 final { }; struct Derived1 : Base1 { }; // mal formado porque la clase Base1 ha sido marcada como final
estructura Base2 { virtual void f () final ; }; struct 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;
declaración declara una nueva función virtual, pero también impide que las clases derivadas la anulen. También tiene el efecto de impedir que las clases derivadas utilicen esa combinación de nombre de función y parámetro en particular.
Ni override
ni ni final
son palabras clave del lenguaje. Técnicamente son identificadores de atributos declaradores:
A los efectos de esta sección y solo de esta sección, cada ocurrencia de " 0
" se entiende como "una expresión constante que evalúa como 0
, que es de tipo int". En realidad, la expresión constante puede ser de cualquier tipo entero.
Desde los albores de C en 1972, la constante ha tenido el doble papel de constante entera y constante de puntero nulo . La ambigüedad inherente al doble significado de
0
se solucionó 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 puntero, eliminando así el beneficio de la conversión 0
a void *
. Como consecuencia, solo 0
se permite como constante de puntero nulo. Esto interactúa mal con la sobrecarga de funciones :
vacío foo ( char * ); vacío foo ( int );
Si NULL
se define como 0
(lo que suele ser el caso en C++), la declaración foo(NULL);
llamará a foo(int)
, lo que casi con certeza no es lo que pretendía el programador y no es lo que sugiere una lectura superficial del código.
C++11 corrige esto introduciendo una nueva palabra clave que sirve como una constante de puntero nulo distinguida: 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 o comparable a los tipos integrales, excepto bool
. Si bien la propuesta original especificaba que un valor r de tipo nullptr_t
no debería ser convertible a bool
, el grupo de trabajo del lenguaje central decidió que tal conversión sería deseable, por coherencia con los tipos de puntero regulares. Los cambios de redacción propuestos fueron votados por unanimidad en el Documento de trabajo en junio de 2008. [1] Una propuesta similar también fue presentada al grupo de trabajo de estándares de C y fue aceptada para su inclusión en C23 . [17]
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 ; // error foo ( nullptr ); // llama a foo(nullptr_t), no a foo(int); /* Tenga en cuenta que foo(nullptr_t) en realidad llamará a foo(char *) en el ejemplo anterior utilizando una conversión implícita, solo si no hay otras funciones que se sobrecarguen con tipos de puntero compatibles en el ámbito. Si existen múltiples sobrecargas, la resolución fallará ya que es ambigua, a menos que haya una declaración explícita de foo(nullptr_t). 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 que NULL se defina como 0 typedef void *nullptr_t; // ANSI C que define NULL como ((void*)0) */
En C++03, las enumeraciones no son de tipo seguro. Son efectivamente 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; por lo tanto, el código que depende del tamaño de la enumeración no es portable. Por último, los valores de enumeración tienen como ámbito el ámbito que los encierra. Por lo tanto, no es posible que dos enumeraciones separadas en el mismo ámbito tengan nombres de miembro coincidentes.
C++11 permite una clasificación especial de enumeración que no presenta ninguno de estos problemas. Esto se expresa mediante la declaración enum class
( enum struct
también se acepta como sinónimo):
clase enum Enumeración { Val1 , Val2 , Val3 = 100 , Val4 // = 101 };
Esta enumeración es de tipo seguro. Los valores de 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
genera un error de compilación).
El tipo subyacente de las clases de enumeración siempre se conoce. El tipo predeterminado es int
; se puede reemplazar por un tipo integral diferente, como se puede ver en este ejemplo:
enumeración clase 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 clase de enumeración. Por lo tanto, en el ejemplo anterior, Val1
no está definido, pero sí Enum2::Val1
está definido.
También hay una sintaxis transicional que permite 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 ámbito de la enumeración ( Enum3::Val1
), pero para compatibilidad con versiones anteriores también se colocan en el ámbito circundante.
En C++11 también es posible declarar enumeraciones de forma anticipada. Antes, los tipos de enumeración no se podían declarar de forma anticipada 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 de forma implícita o explícita, se puede declarar de forma anticipada:
enum Enum1 ; // No válido en C++03 y C++11; no se puede determinar el tipo subyacente. enum Enum2 : unsigned int ; // Válido en C++11, el tipo subyacente se especifica explícitamente. enum class Enum3 ; // Válido en C++11, el tipo subyacente es int. enum class Enum4 : unsigned int ; // Válido en C++11. enum Enum2 : unsigned short ; // No válido en C++11, porque Enum2 se declaró anteriormente con un tipo subyacente diferente.
El analizador sintáctico de C++03 define “ >>
” como el operador de desplazamiento a la derecha o el operador de extracción de flujo en todos los casos. Sin embargo, con declaraciones de plantillas anidadas, existe una tendencia a que el programador descuide la colocación de un espacio entre los dos corchetes angulares derechos, lo que provoca un error de sintaxis del compilador.
C++11 mejora la especificación del analizador para que varios corchetes angulares derechos se interpreten como el cierre de la lista de argumentos de la plantilla cuando sea razonable. Esto se puede anular utilizando paréntesis alrededor de las expresiones de parámetros utilizando los operadores binarios “ >
”, “ >=
” o “ >>
”:
plantilla < bool Test > clase SomeType ; std :: vector < SomeType < 1 > 2 >> x1 ; // Interpretado como un std::vector de SomeType<true>, // seguido por "2 >> x1", que no es una sintaxis válida para un declarador. 1 es verdadero. std :: vector < SomeType < ( 1 > 2 ) >> x1 ; // Interpretado como std::vector de SomeType<false>, // seguido por el declarador "x1", que es una sintaxis válida de C++11. (1>2) es falso.
C++98 agregó la explicit
palabra clave como modificador en los constructores para evitar que los constructores de un solo argumento se usen como operadores de conversión de tipo 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()
para permitirle 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 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 las instrucciones if y los bucles, y los operandos de los operadores lógicos) cuentan como conversiones explícitas y, por lo tanto, pueden usar un operador de conversión booleano.
Por ejemplo, esta función resuelve de forma limpia el problema del valor booleano seguro.
En C++03, es posible definir un typedef únicamente como sinónimo de otro tipo, incluido un sinónimo de una especialización de plantilla con todos los argumentos de plantilla reales especificados. No es posible crear una plantilla typedef. Por ejemplo:
plantilla < typename Primero , typename Segundo , int Tercero > clase SomeType ; plantilla < typename Second > typedef SomeType < OtherType , Second , 5 > TypedefName ; // No válido en C++03
Esto no se compilará.
C++11 agrega esta capacidad con esta sintaxis:
plantilla < typename Primero , typename Segundo , int Tercero > clase SomeType ; plantilla < typename Segundo > usando TypedefName = SomeType < OtherType , Second , 5 > ;
La using
sintaxis también se puede utilizar como alias de tipo en C++11:
typedef void ( * FunctionType )( double ); // Estilo antiguo usando FunctionType = void ( * )( double ); // Nueva sintaxis introducida
En C++03, existen restricciones sobre qué tipos de objetos pueden ser miembros de una unión union
. Por ejemplo, las uniones no pueden contener ningún objeto que defina un constructor o destructor no trivial. C++11 elimina algunas de estas restricciones. [2]
Si un union
miembro tiene una función miembro especial no trivial , el compilador no generará la función miembro equivalente para ella union
y deberá definirse manualmente.
Este es un ejemplo simple de una unión permitida en C++11:
#include <new> // Necesario para la colocación 'nueva'. struct Punto { Punto () {} Punto ( int x , int y ) : x_ ( x ), y_ ( y ) {} int x_ , y_ ; }; union U { int z ; double w ; Point 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 ) {} // Construye el objeto Point usando la lista de inicializadores. U & operator = ( const Point & pt ) { new ( & p ) Point ( pt ); return * this ; } // Asigna el objeto Point usando la ubicación 'new'. };
Los cambios no romperán ningún código existente ya que sólo relajan las reglas actuales.
Estas características permiten que el lenguaje haga cosas que antes eran imposibles, excesivamente detalladas o que requerían bibliotecas no portables.
En C++11, las plantillas pueden aceptar cantidades variables de parámetros de plantilla. Esto también permite la definición de funciones variádicas con seguridad de tipos .
C++03 ofrece dos tipos de literales de cadena . El primer tipo, contenido entre comillas dobles, produce una matriz terminada en nulo de tipo const char
. El segundo tipo, definido como L""
, produce una matriz terminada en nulo de tipo const wchar_t
, donde wchar_t
es un carácter ancho de tamaño y semántica indefinidos. Ninguno de los dos tipos de literales ofrece compatibilidad con literales de cadena con UTF-8 , UTF-16 o cualquier otro tipo de codificación Unicode .
C++11 admite tres codificaciones Unicode: UTF-8, UTF-16 y UTF-32 . La definición del tipo 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 es lo suficientemente grande como para contener cualquier miembro del conjunto de caracteres de ejecución básica del compilador. Anteriormente se definía solo como este último en el propio estándar de C++, y luego dependía del estándar de C para garantizar al menos 8 bits. Además, 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 las codificaciones admitidas se puede realizar de la siguiente manera:
u8 "Soy una cadena UTF-8". u "Esta es una cadena UTF-16". U "Esta es una cadena UTF-32".
El tipo de la primera cadena es el habitual const char[]
. El tipo de la segunda cadena es const char16_t[]
(observe el prefijo "u" en minúscula). El tipo de la tercera cadena es const char32_t[]
(prefijo "U" en mayúscula).
Al crear cadenas literales Unicode, suele ser útil insertar puntos de código Unicode directamente en la cadena. Para ello, C++11 permite esta sintaxis:
u8 "Este es un carácter Unicode: \u2018 ." u "Este es un carácter Unicode más grande: \u2018 ." U "Este es un carácter Unicode: \U00002018 ."
El número que sigue a \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, utilice \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.
A veces también resulta útil evitar el escape manual de cadenas, en particular para el uso de literales de archivos XML , lenguajes de programación o expresiones regulares. C++11 proporciona un literal de cadena sin formato:
R"(La cadena Datos \ Cosas " )"R"delimitador(La cadena Datos \ Cosas " )delimitador"
En el primer caso, todo lo que se encuentra entre "(
y )"
forma parte de la cadena. No es necesario escapar los caracteres "
y . En el segundo caso, comienza la cadena y solo termina cuando se alcanza . La cadena puede ser cualquier cadena de hasta 16 caracteres de longitud, incluida la cadena vacía. Esta cadena no puede contener espacios, caracteres de control, , ni el carácter . Con esta cadena delimitadora, el usuario puede tener la secuencia dentro de literales de cadena sin formato. Por ejemplo, es equivalente a .\
"delimiter(
)delimiter"
delimiter
(
)
\
)"
R"delimiter("(a-z)")delimiter"
"\"(a-z)\""
Los literales de cadena sin formato se pueden combinar con el literal ancho o cualquiera de los prefijos literales Unicode:
u8R"XXX(Soy una cadena "UTF-8 sin formato").XXX"uR"*(Esta es una cadena "UTF-16 sin procesar").*"UR"(Esta es una cadena "UTF-32 sin procesar").
C++03 proporciona una serie de literales. Los caracteres 12.5
son un literal que el compilador resuelve como un tipo double
con el valor 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 de C++ y el código de C++03 no puede crear nuevos modificadores de literales.
Por el contrario, C++11 permite al usuario definir nuevos tipos de modificadores literales que construirán objetos basados en la cadena de caracteres que modifica el literal.
La transformación de literales se redefine en dos fases distintas: raw y cooking. Un literal raw es una secuencia de caracteres de un tipo específico, mientras que el literal cooking es de un tipo distinto. El literal de C++ 1234
, como literal raw, es esta secuencia de caracteres '1'
, '2'
, '3'
, '4'
. Como literal cooking, es el entero 1234. El literal de C++ 0xA
en forma raw es '0'
, 'x'
, 'A'
, mientras que en forma cooking es el entero 10.
Los literales se pueden extender tanto en forma cruda como en forma preparada, con la excepción de los literales de cadena, que solo se pueden procesar en forma preparada. 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 comiencen con cualquier carácter excepto el guión bajo ( _
) están reservados por el estándar. Por lo tanto, todos los literales definidos por el usuario deben tener sufijos que comiencen con un guión bajo ( _
). [18]
Los literales definidos por el usuario que procesan la forma original del literal se definen mediante un operador literal, que se escribe como operator ""
. A continuación, se muestra un ejemplo:
Operador OutputType "" _mysuffix ( const char * literal_string ) { // asume que OutputType tiene un constructor que toma una const char* OutputType ret ( literal_string ); return ret ; } OutputType some_variable = 1234 _mysuffix ; // asume que OutputType tiene un método get_value() que devuelve una doble aserción ( some_variable . get_value () == 1234.0 )
La instrucció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 sin formato de números enteros y de punto flotante es a través de una plantilla variádica :
plantilla < char ... > Operador de tipo de salida "" _tuffix (); Tipo de salida alguna_variable = 1234 _tuffix ; Tipo de salida otra_variable = 2.17 _tuffix ;
Esto crea una instancia de la función de procesamiento literal como operator "" _tuffix<'1', '2', '3', '4'>()
. En este formato, no hay ningún carácter nulo que finalice la cadena. El objetivo principal de hacer esto es usar constexpr
la palabra clave de C++11 para garantizar que el compilador transformará el literal por completo en el momento de la compilación, suponiendo que OutputType
es un tipo construible y copiable constexpr, y que la función de procesamiento literal es una constexpr
función.
En el caso de los literales numéricos, el tipo del literal cocinado es unsigned long long
para literales integrales o long double
para literales de punto 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 existe una forma de plantilla alternativa:
Operador OutputType "" _suffix ( unsigned long long ); Operador OutputType "" _suffix ( long double ); OutputType some_variable = 1234 _suffix ; // Utiliza la sobrecarga 'unsigned long long'. OutputType another_variable = 3.1416 _suffix ; // Utiliza la sobrecarga 'long double'.
De acuerdo con los nuevos prefijos de cadena mencionados anteriormente, para los literales de cadena, se utilizan estos:
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 some_variable = u8 " 1234" _ssuffix ; // Utiliza la sobrecarga 'const char *'. OutputType some_variable = L "1234" _ssuffix ; // Utiliza la sobrecarga 'const wchar_t *'. OutputType some_variable = u "1234" _ssuffix ; // Utiliza la sobrecarga 'const char16_t *'. OutputType some_variable = U "1234" _ssuffix ; // Utiliza la sobrecarga 'const char32_t *'.
No existe una plantilla alternativa. Los literales de caracteres se definen de manera similar.
C++11 estandariza el soporte para programación multiproceso .
Hay dos partes involucradas: un modelo de memoria que permite que varios subprocesos coexistan en un programa y soporte de biblioteca para la interacción entre subprocesos. (Consulte la sección de este artículo sobre las funciones de subprocesos).
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.
En un entorno multiproceso, es habitual que cada subproceso tenga algunas variables únicas . Esto ya sucede con las variables locales de una función, pero no sucede con las variables globales y estáticas.
El especificador de almacenamiento indica una nueva duración de almacenamiento local del hilo (además de las existentes static , dynamic y automaticthread_local
) .
A cualquier objeto que pueda tener una duración de almacenamiento estática (es decir, una vida útil que abarque toda la ejecución del programa) se le puede asignar una duración local del subproceso. La intención es que, como cualquier otra variable de duración estática, un objeto local del subproceso se pueda inicializar utilizando un constructor y destruir utilizando un destructor.
En C++03, el compilador proporciona, para las clases que no los proporcionan por sí mismas, un constructor predeterminado, un constructor de copia, un operador de asignación de copia ( operator=
) y un destructor. El programador puede anular estos valores predeterminados definiendo versiones personalizadas. C++ también define varios operadores globales (como operator new
) que funcionan en todas las clases y que el programador puede anular.
Sin embargo, hay muy poco control sobre la creación de estos valores predeterminados. Por ejemplo, para que una clase sea inherentemente no copiable, se puede declarar un constructor de copia privado y un operador de asignación de copia 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, [19] las violaciones pueden dar como resultado 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 constructor predeterminado generado por el compilador.
C++11 permite la configuración predeterminada y la eliminación explícita de estas funciones miembro especiales. [20] Por ejemplo, esta clase declara explícitamente que se puede utilizar un constructor predeterminado:
clase SomeType { SomeType () = predeterminado ; //El constructor predeterminado se indica explícitamente. SomeType ( OtherType valor ); };
Una función se puede deshabilitar explícitamente. Esto resulta útil para evitar conversiones de tipos implícitas. El = delete
especificador se puede utilizar para prohibir la llamada a una función con tipos de parámetros particulares. [20] Por ejemplo:
void noInt ( double i ); void noInt ( int ) = eliminar ;
El compilador rechazará un intento de llamar noInt()
con un parámetro, en lugar de realizar una conversión silenciosa a . Llamar con un todavía funciona.int
double
noInt()
float
Es posible prohibir la llamada a la función con cualquier tipo excepto double
mediante el uso de una plantilla:
doble soloDoble ( doble d ) { devolver d ;} plantilla < typename T > doble soloDoble ( T ) = eliminar ;
La llamada onlyDouble(1.0)
funcionará, mientras que onlyDouble(1.0f)
generará un error de compilación.
También se pueden eliminar las funciones miembro y los constructores de una clase. Por ejemplo, es posible evitar la copia de objetos de clase eliminando el constructor de copia y operator =
:
clase No Copiable { No Copiable (); No Copiable ( const No Copiable & ) = eliminar ; No Copiable & operador = ( const No Copiable & ) = eliminar ; };
long long int
En C++03, el tipo entero más grande es long int
. Se garantiza que tiene al menos tantos bits utilizables como int
. Esto dio como resultado long int
un tamaño de 64 bits en algunas implementaciones populares y 32 bits en otras. C++11 agrega un nuevo tipo entero long long int
para abordar este problema. Se garantiza que sea al menos tan grande como un long int
, y que tenga no menos de 64 bits. El tipo fue introducido originalmente por C99 en el C estándar, y la mayoría de los compiladores de C++ ya lo admitían como una extensión. [21] [22]
C++03 ofrece dos métodos para probar afirmaciones : la macro assert
y la directiva de preprocesador #error
. Sin embargo, ninguno de ellos es adecuado para su uso en plantillas: la macro prueba la afirmación en el momento de la ejecución, mientras que la directiva de preprocesador prueba la afirmación durante el preprocesamiento, que se produce antes de la creación de instancias de las plantillas. Ninguno de ellos es adecuado para probar propiedades que dependen de los parámetros de la plantilla.
La nueva utilidad introduce una nueva forma de probar afirmaciones en tiempo de compilación, utilizando la palabra clave new 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!" );
plantilla < clase T > struct Check { static_assert ( sizeof ( int ) <= sizeof ( T ), "¡T no es lo suficientemente grande!" ); };
plantilla < class Integral > Integral foo ( Integral x , Integral y ) { static_assert ( std :: is_integral < Integral >:: value , "el parámetro foo() debe ser de tipo integral." ); }
Cuando la expresión constante es , false
el compilador produce un mensaje de error. El primer ejemplo es similar a la directiva del preprocesador #error
, aunque el preprocesador solo admite tipos integrales. [23] Por el contrario, en el segundo ejemplo, la afirmación se comprueba en cada instancia de la clase de plantilla Check
.
Las afirmaciones estáticas también son útiles fuera de las plantillas. Por ejemplo, una implementación determinada de un algoritmo podría depender de que el tamaño de un long long
sea mayor 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.
sizeof
trabajar en miembros de clases sin un objeto explícitoEn C++03, el sizeof
operador se puede utilizar en tipos y objetos, pero no se puede utilizar para hacer lo siguiente:
struct SomeType { OtherType miembro ; }; sizeof ( SomeType :: member ); // No funciona con C++03. Funciona bien con C++11
Esto debería devolver el tamaño de OtherType
. C++03 no lo permite, 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.
C++11 permite consultar y controlar la alineación de variables con alignof
y alignas
.
El alignof
operador toma el tipo y devuelve el límite de bytes de potencia 2 en el que se deben asignar las instancias del tipo (como std::size_t
). Cuando se proporciona un tipo de referencia, alignof
devuelve la alineación del tipo referenciado; 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 una forma abreviada de alignas(alignof(T))
. Por ejemplo, para especificar que una matriz de caracteres debe estar correctamente alineada para contener un flotante:
alignas ( float ) unsigned char c [ sizeof ( float )]
Los estándares anteriores de C++ preveían la recolección de basura dirigida por el programador mediante set_new_handler
, pero no brindaban 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 manera segura" de otros valores. Una implementación puede especificar que opera bajo estricta seguridad de puntero , en cuyo caso los punteros que no se derivan de acuerdo con estas reglas pueden volverse inválidos.
C++11 proporciona una sintaxis estandarizada para las extensiones de compiladores y herramientas del lenguaje. Estas extensiones se especificaban tradicionalmente mediante #pragma
directivas o palabras clave específicas del proveedor (como __attribute__
en el caso de GNU y __declspec
Microsoft). Con la nueva sintaxis, se puede especificar información adicional en forma de un 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
se aplica al tipo de variable i
y attr2
se attr3
aplica a la variable misma, attr4
se aplica a la if
declaración y vendor::attr5
se aplica a la declaració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 pueden incluirse 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 significado semántico en el lenguaje y que no cambien el sentido de un programa cuando se los ignora. 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: noreturn
para especificar que una función no retorna 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 ]
Se introdujeron varias características nuevas en la biblioteca estándar de C++11. Muchas de ellas podrían haberse implementado con el estándar anterior, pero algunas dependen (en mayor o menor medida) de las nuevas características básicas de C++11.
Una gran parte de las nuevas bibliotecas se definió en el documento C++ Standards Committee's Library Technical Report (denominado TR1), que se publicó en 2005. Actualmente, hay disponibles varias implementaciones completas y parciales de TR1 que utilizan el espacio de nombres std::tr1
. Para C++11 se trasladaron al espacio de nombres std
. Sin embargo, a medida que se incorporaron características de TR1 a la biblioteca estándar de C++11, se actualizaron cuando fue necesario con características del lenguaje C++11 que no estaban disponibles en la versión inicial de TR1. Además, es posible que se hayan mejorado con características que eran posibles en C++03, pero que no formaban parte de la especificación TR1 original.
C++11 ofrece una serie de nuevas características del lenguaje de las que pueden beneficiarse los componentes de la biblioteca estándar existentes actualmente. Por ejemplo, la mayoría de los contenedores de la biblioteca estándar pueden beneficiarse de la compatibilidad con el constructor de movimiento basado en la referencia 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 necesario. Estas incluyen, entre otras:
decltype
explicit
operadores de conversiónAdemás, ha pasado mucho tiempo desde el estándar C++ anterior. Se ha escrito mucho código que utiliza la biblioteca estándar. Esto ha revelado partes de las bibliotecas estándar que podrían mejorarse. Entre las muchas áreas de mejora consideradas estaban los asignadores de biblioteca estándar . En C++11 se incluyó un nuevo modelo de asignadores basado en el alcance para complementar el modelo anterior.
Si bien el lenguaje C++03 proporciona un modelo de memoria que admite subprocesos, el soporte principal para el uso real de subprocesos proviene de la biblioteca estándar de C++11.
std::thread
Se proporciona una clase de subproceso ( ), que toma un objeto de función (y una serie opcional de argumentos para pasarle) para ejecutarse en el nuevo subproceso. Es posible hacer que un subproceso se detenga hasta que se complete otro subproceso en ejecución, lo que proporciona soporte para unirse a subprocesos a través de la std::thread::join()
función miembro. Se proporciona acceso, cuando es posible, a los objetos de subproceso nativos subyacentes para operaciones específicas de la plataformastd::thread::native_handle()
mediante la función miembro.
Para la sincronización entre subprocesos, se agregan a la biblioteca los mutex apropiados ( std::mutex
, std::recursive_mutex
, etc.) y las variables de condición ( std::condition_variable
y ). Se puede acceder a ellos a través de bloqueos de adquisición de recursos como inicialización (RAII) ( y ) y algoritmos de bloqueo para facilitar su uso.std::condition_variable_any
std::lock_guard
std::unique_lock
Para trabajos de bajo nivel y alto rendimiento, a veces es necesaria la comunicación entre subprocesos sin la sobrecarga de los mutex. Esto se hace mediante operaciones atómicas en ubicaciones de memoria. Estas pueden especificar opcionalmente las restricciones mínimas de visibilidad de memoria necesarias para una operación. También se pueden utilizar barreras de memoria explícitas para este 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 encapsular una llamada de función que pueda generar dicho resultado asincrónico. La propuesta de futuros fue criticada porque carece de una forma de combinar futuros y verificar la finalización de una promesa dentro de un conjunto de promesas. [24]
Otras funciones de subprocesos de alto nivel, como los grupos de subprocesos, se han remitido a un futuro informe técnico de C++ . No forman parte de C++11, pero se espera que su implementación final se base completamente en las características de la biblioteca de subprocesos.
La nueva std::async
funció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 asincrónica 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 forma sencilla de aprovechar la concurrencia de hardware sin sobresuscripción y proporciona algunas de las ventajas de un grupo de subprocesos para usos simples.
Las tuplas son conjuntos compuestos 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 variádicas. Para implementarla de manera razonable, la versión TR1 requería un número máximo definido por la implementación de los tipos contenidos y un gran número de trucos con las macros. Por el contrario, la implementación de la versión C++11 no requiere un número máximo de tipos definido por la implementación explícita. Aunque los compiladores tendrán una profundidad de recursión máxima interna para la instanciación de plantillas (lo cual es normal), la versión C++11 de las tuplas no expondrá este valor al usuario.
Usando plantillas variádicas , 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 :: tupla < int , double , long & , const char *> test_tuple ; long lengthy = 12 ; test_tuple proof ( 18 , 6.5 , lengthy , "¡Ciao!" ); lengthy = std :: get < 0 > ( proof ); // Asigna a 'lengthy' el valor 18. std :: get < 3 > ( proof ) = " Beautiful!" ; // Modifica el cuarto elemento de la tupla.
Es posible crear la tupla proof
sin definir su contenido, pero sólo si los tipos de los elementos de la tupla poseen constructores por defecto. 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 primeros dos 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 std::make_tuple
la posibilidad de crear automáticamente std::tuple
s mediante la deducción de tipos y auto
ayuda a declarar dicha tupla. std::tie
crea tuplas de referencias de valor l para ayudar a descomprimir tuplas. std::ignore
También ayuda en este caso. Vea el ejemplo:
auto record = std :: make_tuple ( "Hari Ram" , "Nueva Delhi" , 3.5 , 'A' ); std :: string nombre ; float promedio ; char calificación ; std :: tie ( nombre , std :: ignorar , promedio , calificación ) = record ; // std::ignore ayuda a eliminar el nombre del lugar std :: cout << nombre << ' ' << promedio << ' ' << calificación << std :: endl ;
Hay operadores relacionales disponibles (entre tuplas con el mismo número de elementos) y hay dos expresiones disponibles para comprobar las características de una tupla (solo durante la compilación):
std::tuple_size<T>::value
devuelve el número de elementos en la tupla T
,std::tuple_element<I, T>::type
devuelve el tipo del objeto número I
de la tupla T
.La inclusión de tablas hash (contenedores asociativos no ordenados) en la biblioteca estándar de C++ es una de las peticiones más recurrentes. No se adoptó en C++03 debido únicamente a limitaciones de tiempo. Aunque las tablas hash son menos eficientes que un árbol balanceado 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 ha considerado 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 “unordered” en lugar de “hash”.
La nueva biblioteca cuenta con cuatro tipos de tablas hash, diferenciadas por si aceptan o no elementos con la misma clave (claves únicas o claves equivalentes) y si asignan a cada clave un valor asociado. Corresponden a los cuatro contenedores asociativos existentes basados en árboles binarios de búsqueda , con prefijo unordered_ .
Las nuevas clases cumplen todos los requisitos de una clase contenedora y tienen todos los métodos necesarios para acceder a los elementos: insert
, erase
, begin
, end
.
Esta nueva característica no requirió 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 <functional>
y la introducción de los encabezados <unordered_set>
y <unordered_map>
. No se necesitaron otros cambios en ninguna clase estándar existente y no depende de ninguna otra extensión de la biblioteca estándar.
Además de las tablas hash, se agregaron dos contenedores más a la biblioteca estándar. El std::array es un contenedor de tamaño fijo que es más eficiente que el std::vector, pero más seguro y fácil de usar que una matriz de estilo C. El std::forward_list es una lista enlazada simple que proporciona un almacenamiento más eficiente en términos de espacio que el std::list enlazado doblemente cuando no se necesita la iteración bidireccional.
La nueva biblioteca, definida en el nuevo encabezado <regex>
, está compuesta por un par de nuevas clases:
std::regex
;std::match_results
,La función std::regex_search
se utiliza para buscar, mientras que para 'buscar y reemplazar' std::regex_replace
se utiliza la función que devuelve una nueva cadena. [25]
A continuación se muestra un ejemplo del uso de std::regex_iterator
:
#include <regex> const char * patrón = R " ( [^ ,.\t\n]+ ) " ; // buscar palabras separadas por espacio, coma, punto tabulación nueva línea std :: regex rgx ( patrón ); // lanza una excepción en un patrón no válido const char * target = "Universidad Invisible - Ankh-Morpork" ; // Utilice un regex_iterator para identificar todas las palabras de 'target' separadas por caracteres de 'pattern'. auto iter = std :: cregex_iterator ( target , target + strlen ( target ), rgx ); // hacer un iterador de fin de secuencia auto end = std :: cregex_iterator (); para (; iter != fin ; ++ iter ) { std :: cadena match_str = iter -> str (); std :: cout << match_str << '\n' ; }
La biblioteca <regex>
no requiere la modificación de ningún encabezado existente (aunque los utilizará cuando sea necesario) ni una extensión del lenguaje principal. En POSIX C, las expresiones regulares también están disponibles a través de la biblioteca C POSIX #regex.h .
C++11 proporciona std::unique_ptr
, y mejoras desde std::shared_ptr
y std::weak_ptr
hacia TR1. std::auto_ptr
está obsoleto.
La biblioteca estándar de C ofrece la posibilidad de generar números pseudoaleatorios mediante la función rand
. Sin embargo, el algoritmo se delega por completo en el proveedor de la biblioteca. C++ heredó esta funcionalidad sin cambios, pero C++11 ofrece 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 generador base:
C++11 también proporciona una serie de distribuciones estándar:
uniform_int_distribution
,uniform_real_distribution
,bernoulli_distribution
,binomial_distribution
,geometric_distribution
,negative_binomial_distribution
,poisson_distribution
,exponential_distribution
,gamma_distribution
,weibull_distribution
,extreme_value_distribution
,normal_distribution
,lognormal_distribution
,chi_squared_distribution
,cauchy_distribution
,fisher_f_distribution
,student_t_distribution
,discrete_distribution
,piecewise_constant_distribution
ypiecewise_linear_distribution
.El generador y las distribuciones se combinan como en este ejemplo:
#include <aleatorio> #include <funcional> std :: uniform_int_distribution < int > distribución ( 0,99 ) ; std :: mt19937 motor ; // Mersenne twister MT19937 auto generador = std :: bind ( distribución , motor ); int aleatorio = generador (); // Generar una variable integral uniforme entre 0 y 99. int aleatorio2 = distribución ( motor ); // Generar otra muestra directamente usando los objetos distribución y motor.
Una referencia de contenedor se obtiene de una instancia de la clase template reference_wrapper
. Las referencias de contenedor son similares a las referencias normales (' &
') del lenguaje C++. Para obtener una referencia de contenedor de cualquier objeto ref
se utiliza la función template (para una referencia constante cref
se utiliza).
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 tomará una referencia al parámetro 'r' y lo incrementará. void func ( int & r ) { r ++ ; } // Función de plantilla. plantilla < clase F , clase P > void g ( F f , P t ) { f ( t ); } int main () { int i = 0 ; g ( func , i ); // Se crea una instancia de 'g<void (int &r), int>' // entonces 'i' no se modificará. std :: cout << i << std :: endl ; // Salida -> 0 g ( func , std :: ref ( i )); // Se crea una instancia de 'g<void(int &r),reference_wrapper<int>>' // luego se modificará 'i'. std :: cout << i << std :: endl ; // Salida -> 1 }
Esta nueva utilidad se agregó al <functional>
encabezado existente y no necesitó más extensiones del lenguaje C++.
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 estrechamente ligados y pueden referirse indiscriminadamente a cualquier cosa que pueda ser llamada (punteros de función, punteros de función miembro o functores) cuyos argumentos sean compatibles con los del envoltorio.
Un ejemplo puede aclarar sus características:
std :: function < int ( int , int ) > func ; // Creación del contenedor usando // la clase de plantilla 'function'. std :: plus < int > add ; // 'plus' se declara como 'template<class T> T plus( T, T ) ;' // entonces 'add' es del tipo 'int add( int x, int y )'. func = add ; // OK - Los parámetros y los tipos de retorno son los mismos. int a = func ( 1 , 2 ); // NOTA: si el contenedor 'func' no hace referencia a ninguna función, // se lanza la excepción 'std::bad_function_call'. std :: function < bool ( short , short ) > func2 ; if ( ! func2 ) { // Verdadero porque a 'func2' aún no se le ha asignado una función. bool adyacente ( long x , long y ); func2 = & adyacente ; // OK - Los parámetros y tipos de retorno son convertibles. struct Test { bool operator () ( short x , short y ); }; Test car ; func = std :: ref ( car ); // 'std::ref' es una función de plantilla que devuelve el contenedor // de la función miembro 'operator()' de struct 'car'. } func = func2 ; // OK - Los parámetros y los tipos de retorno son convertibles.
La clase de plantilla function
se definió dentro del encabezado <functional>
, sin necesidad de ningún cambio en el lenguaje C++.
La metaprogramación consiste en crear un programa que crea o modifica otro programa (o a sí mismo). Esto puede ocurrir durante la compilación o durante la ejecución. El Comité de estándares de C++ ha decidido introducir una biblioteca para la metaprogramación durante la compilación mediante plantillas.
A continuación se muestra un ejemplo de un metaprograma que utiliza el estándar C++03: una recursión de instancias de plantilla para calcular exponentes enteros:
plantilla < int B , int N > struct Pow { // llamada recursiva y recombinación. enum { valor = B * Pow < B , N -1 >:: valor }; }; plantilla < 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 distintos tipos de datos; las plantillas de C++ admiten la programación genérica y hacen que el código sea más compacto y útil. Sin embargo, es común que los algoritmos necesiten información sobre los tipos de datos que se utilizan. Esta información se puede extraer durante la instanciación de una clase de plantilla mediante rasgos de tipo .
Los rasgos de tipo pueden identificar la categoría de un objeto y todas las características de una clase (o de una estructura). Se definen en el nuevo encabezado <type_traits>
.
En el siguiente ejemplo está la función de plantilla 'elaborate' que, dependiendo de los tipos de datos dados, instanciará uno de los dos algoritmos propuestos ( Algorithm::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 Algorithm < true > { plantilla < clase T1 , clase T2 > static int do_it ( T1 , T2 ) { /*...*/ } }; // Al crear una instancia de 'elaborate' se creará automáticamente la forma correcta de operar. template < class T1 , class T2 > int brew ( T1 A , T2 B ) { // Use la segunda forma solo si 'T1' es un entero y si 'T2' está // en punto flotante, de lo contrario use la primera forma. return Algorithm < 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 <type_traits>
, 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 código elegante y conciso; sin embargo, el punto débil de estas técnicas es la depuración: es incómoda durante la compilación y muy difícil durante la ejecución del programa.
Determinar el tipo de retorno de un objeto de función de plantilla en tiempo de compilación no es intuitivo, en particular si el valor de retorno depende de los parámetros de la función. A modo de ejemplo:
struct Clear { int operator ()( int ) const ; // El tipo del 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 { devolver miembro ( a ); } privado : miembro Obj ; };
Al crear una instancia de la plantilla de clase Calculus<Clear>
, 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 Confused
siguiente:
struct Confused { double operator ()( int ) const ; // El tipo del parámetro no es int operator ()( double ) const ; // igual al tipo de retorno. };
Si se intenta crear una instancia, Calculus<Confused>
el tipo de retorno de Calculus
no será el mismo que el de la clase 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
utiliza el std::result_of
objeto para derivar el tipo de retorno del objeto de función:
plantilla < clase Obj > clase CalculusVer2 { público : plantilla < clase Arg > tiponombre std :: resultado_de < Obj ( Arg ) >:: operador de tipo () ( Arg & a ) const { devolver miembro ( a ); } privado : Obj miembro ; };
De esta manera en instancias del objeto de función CalculusVer2<Confused>
no hay conversiones, advertencias ni errores.
El único cambio con respecto a la versión TR1 de std::result_of
es que la versión TR1 permitía que una implementación no pudiera determinar el tipo de resultado de una llamada de función. Debido a los cambios en C++ para admitir decltype
, la versión C++11 de std::result_of
ya no necesita estos casos especiales; se requiere que las implementaciones calculen un tipo en todos los casos.
Para compatibilidad con C , a partir de C99, se agregaron: [26]
_Pragma()
– equivalente de #pragma
.long long
– tipo entero que tiene al menos 64 bits de longitud.__func__
– macro que evalúa el nombre de la función en la que se encuentra.cstdbool
( stdbool.h
),cstdint
( stdint.h
),cinttypes
( inttypes.h
).Rumbo a un TR independiente:
Aplazado:
Se eliminó el término punto de secuencia y se reemplazó por la especificación de que una operación se secuencia antes de otra, o que dos operaciones no están secuenciadas. [28]
export
Se eliminó el uso anterior de la palabra clave . [29] La palabra clave en sí permanece, reservada para un posible uso futuro.
Las especificaciones de excepciones dinámicas están obsoletas. [29] La especificación en tiempo de compilación de funciones que no lanzan excepciones está disponible con la noexcept
palabra clave , que es útil para la optimización.
std::auto_ptr
está obsoleto y ha sido reemplazado por std::unique_ptr
.
Las clases base de objetos de función ( std::unary_function
, std::binary_function
), los adaptadores a punteros a funciones y los adaptadores a punteros a miembros, y las clases de enlace están todas obsoletas.