C++11 [1] [2] o ISO/IEC 14882:2011 [3] (en el proceso de trabajo en el estándar, tenía el nombre en clave C++0x [4] [5] ): una nueva versión de el estándar de lenguaje C++ en lugar del ISO /IEC 14882:2003 previamente válido. El nuevo estándar incluye adiciones al núcleo del lenguaje y una extensión a la biblioteca estándar, incluida la mayor parte de TR1 , excepto quizás la biblioteca de funciones matemáticas especiales. Las nuevas versiones de los estándares, junto con algunos otros documentos de estandarización de C++, se publican en el sitio web del comité ISO C++ [6] . Ejemplos de programación en C++
Los lenguajes de programación están experimentando un desarrollo gradual de sus capacidades (actualmente, después de C++11, se han publicado las siguientes extensiones estándar: C++14, C++17, C++20). Este proceso inevitablemente causa problemas de compatibilidad con el código existente. El Apéndice C.2 [diff.cpp03] del Borrador Final del Estándar Internacional N3290 describe algunas de las incompatibilidades entre C++11 y C++03.
Como ya se mencionó, los cambios afectarán tanto al núcleo de C++ como a su biblioteca estándar.
Al desarrollar cada sección de la futura norma, el comité utilizó una serie de reglas:
Se presta atención a los principiantes, que siempre serán la mayoría de los programadores. Muchos principiantes no buscan profundizar su conocimiento de C ++, limitándose a usarlo cuando trabajan en tareas específicas limitadas [7] . Además, dada la versatilidad de C++ y la amplitud de su uso (que incluye tanto la variedad de aplicaciones como los estilos de programación), incluso los profesionales pueden descubrir nuevos paradigmas de programación .
La tarea principal del comité es desarrollar el núcleo del lenguaje C++. El kernel se ha mejorado significativamente, se ha agregado soporte multiproceso , se ha mejorado el soporte para programación genérica , se ha unificado la inicialización y se ha trabajado para mejorar su rendimiento.
Para mayor comodidad, las características y los cambios del kernel se dividen en tres partes principales: mejoras de rendimiento, mejoras de conveniencia y nueva funcionalidad. Los elementos individuales pueden pertenecer a varios grupos, pero se describirán solo en uno: el más apropiado.
Estos componentes de lenguaje se introducen para reducir la sobrecarga de memoria o mejorar el rendimiento.
Referencias a objetos temporales y semántica de movimientoSegún el estándar C++ , un objeto temporal resultante de la evaluación de una expresión se puede pasar a funciones, pero solo mediante una referencia constante ( const & ). La función no puede determinar si el objeto pasado se puede considerar temporal y modificable (un objeto const que también se puede pasar mediante dicha referencia no se puede modificar (legalmente)). Esto no es un problema para estructuras simples como complex, pero para tipos complejos que requieren asignación o desasignación de memoria, destruir un objeto temporal y crear uno permanente puede llevar mucho tiempo, mientras que uno podría simplemente pasar punteros directamente.
C++11 introduce un nuevo tipo de referencia , la referencia rvalue . Su declaración es: tipo && . Las nuevas reglas de resolución de sobrecarga le permiten usar diferentes funciones sobrecargadas para objetos temporales no constantes, indicados por valores r, y para todos los demás objetos. Esta innovación permite la implementación de la llamada semántica de movimiento .
Por ejemplo, std::vector es un contenedor simple alrededor de una matriz C y una variable que almacena su tamaño. El constructor de copias std::vector::vector(const vector &x)creará una nueva matriz y copiará la información; el constructor de transferencia std::vector::vector(vector &&x)puede simplemente intercambiar punteros y variables que contengan la longitud.
Ejemplo de anuncio.
plantilla < clase T > vector de clase { vector ( const vector & ); // Copiar constructor (lento) vector ( vector && ); // Transferir el constructor desde un objeto temporal (rápido) vector & operator = ( const vector & ); // Asignación regular (lenta) vector & operador = ( vector && ); // Mover objeto temporal (rápido) void foo () & ; // Función que solo funciona en un objeto con nombre (lento) void foo () && ; // Función que funciona solo para un objeto temporal (rápido) };Existen varios patrones asociados con los enlaces temporales, de los cuales los dos más importantes son y . El primero hace que un objeto con nombre regular sea una referencia temporal: moveforward
// std::mover plantilla ejemplo void bar ( std :: string && x ) { estándar estático :: stringsomeString ; _ algunaCadena = std :: mover ( x ); // dentro de la función x=string&, de ahí el segundo movimiento para llamar a la asignación de movimiento } std :: fibroso ; _ barra ( std :: mover ( y )); // el primer movimiento convierte string& en string&& para llamar a la barraLa plantilla se usa solo en metaprogramación, requiere un parámetro de plantilla explícito (tiene dos sobrecargas indistinguibles) y está asociada con dos nuevos mecanismos de C++. El primero es el pegado de enlaces: , luego . En segundo lugar, la función bar() anterior requiere un objeto temporal en el exterior, pero en el interior, el parámetro x es un nombre ordinario (lvalue) para el respaldo, lo que hace que sea imposible distinguir automáticamente el parámetro string& del parámetro string&&. En una función ordinaria sin plantilla, el programador puede o no poner move(), pero ¿qué pasa con la plantilla? forwardusing One=int&&; using Two=One&;Two=int&
// ejemplo de uso de la plantilla std::forward class Obj { std :: campo de cadena ; _ plantilla < claseT > _ Obj ( T && x ) : campo ( std :: adelante < T > ( x )) {} };Este constructor cubre las sobrecargas normales (T=cadena&), copiar (T=cadena constante&) y mover (T=cadena) con pegado de referencia. Y forward no hace nada o se expande a std::move dependiendo del tipo de T, y el constructor copiará si es una copia y moverá si es un movimiento.
Expresiones constantes genéricasC++ siempre ha tenido el concepto de expresiones constantes. Por lo tanto, expresiones como 3+4 siempre arrojaron los mismos resultados sin causar efectos secundarios. Por sí mismas, las expresiones constantes proporcionan una manera conveniente para que los compiladores de C++ optimicen el resultado de la compilación. Los compiladores evalúan los resultados de dichas expresiones solo en el momento de la compilación y almacenan los resultados ya calculados en el programa. Por lo tanto, tales expresiones se evalúan solo una vez. También hay algunos casos en los que el lenguaje estándar requiere el uso de expresiones constantes. Tales casos, por ejemplo, pueden ser definiciones de matrices externas o valores de enumeración.
El código anterior es ilegal en C++ porque GiveFive() + 7 no es técnicamente una expresión constante conocida en tiempo de compilación. El compilador simplemente no sabe en ese momento que la función realmente devuelve una constante en tiempo de ejecución. El motivo de este razonamiento del compilador es que esta función puede afectar el estado de una variable global, llamar a otra función en tiempo de ejecución que no sea constante, etc.
C++11 introduce la palabra clave constexpr , que permite al usuario asegurarse de que una función o un constructor de objetos devuelva una constante de tiempo de compilación. El código anterior podría reescribirse así:
constexpr int GiveFive () { return 5 ;} int some_value [ GiveFive () + 7 ]; // crea una matriz de 12 enteros; permitido en C++11Esta palabra clave permite que el compilador comprenda y verifique que GiveFive devuelve una constante.
El uso de constexpr impone restricciones muy estrictas sobre las acciones de la función:
En la versión anterior del estándar, solo se podían usar variables de tipo entero o enumerado en expresiones constantes. En C++11, esta restricción se levanta para las variables cuya definición está precedida por la palabra clave constexpr:
constexpr doble aceleración de la gravedad = 9.8 ; constexpr double moonGravity = aceleracionDeLaGravedad / 6 ;Dichas variables ya se consideran implícitamente indicadas por la palabra clave const . Solo pueden contener los resultados de expresiones constantes o los constructores de dichas expresiones.
Si es necesario construir valores constantes a partir de tipos definidos por el usuario, los constructores de dichos tipos también se pueden declarar mediante constexpr . Un constructor de expresiones constantes, como las funciones constantes, también debe definirse antes de su primer uso en la unidad de compilación actual. Dicho constructor debe tener un cuerpo vacío, y dicho constructor debe inicializar los miembros de su tipo solo con constantes.
Cambios en la definición de datos simplesEn C++ estándar, solo las estructuras que satisfacen un determinado conjunto de reglas pueden considerarse un tipo de datos simple y antiguo ( POD). Hay buenas razones para esperar que estas reglas se amplíen para que más tipos se consideren POD. Los tipos que cumplen estas reglas se pueden usar en una implementación de capa de objetos compatible con C. Sin embargo, la lista de estas reglas de C++03 es demasiado restrictiva.
C++11 relajará varias reglas con respecto a la definición de tipos de datos simples.
Se considera que una clase es un tipo de datos simple si es trivial , tiene un diseño estándar ( distribución estándar ) y si los tipos de todos sus miembros de datos no estáticos también son tipos de datos simples.
Una clase trivial es una clase que:
Una clase con colocación estándar es una clase que:
En C++ estándar, el compilador debe instanciar una plantilla siempre que encuentre su especialización completa en una unidad de traducción. Esto puede aumentar significativamente el tiempo de compilación, especialmente cuando se crea una instancia de la plantilla con los mismos parámetros en una gran cantidad de unidades de traducción. Actualmente no hay forma de decirle a C++ que no debería haber instancias.
C++11 introdujo la idea de las plantillas externas. C++ ya tiene una sintaxis para decirle al compilador que se debe crear una instancia de una plantilla en un punto determinado:
clase de plantilla std :: vector < MiClase > ;C++ carece de la capacidad de evitar que el compilador cree instancias de una plantilla en una unidad de traducción. C++11 simplemente extiende esta sintaxis:
clase de plantilla externa std :: vector < MyClass > ;Esta expresión le dice al compilador que no cree una instancia de la plantilla en esta unidad de traducción.
Estas características están destinadas a hacer que el lenguaje sea más fácil de usar. Le permiten fortalecer la seguridad de los tipos, minimizar la duplicación de código, dificultar el mal uso del código, etc.
Listas de inicializaciónEl concepto de listas de inicialización llegó a C++ desde C. La idea es que se puede crear una estructura o matriz pasando una lista de argumentos en el mismo orden en que se definen los miembros de la estructura. Las listas de inicialización son recursivas, lo que les permite usarse para arreglos de estructuras y estructuras que contienen estructuras anidadas.
objeto de estructura { flotar primero ; segundo int ; }; Objeto escalar = { 0.43f , 10 }; // un objeto, con first=0.43f y second=10 Object anArray [] = {{ 13.4f , 3 }, { 43.28f , 29 }, { 5.934f , 17 }}; // matriz de tres objetosLas listas de inicialización son muy útiles para listas estáticas y cuando desea inicializar una estructura a un valor específico. C++ también contiene constructores, que pueden contener el trabajo general de inicializar objetos. El estándar C++ permite el uso de listas de inicialización para estructuras y clases, siempre que se ajusten a la definición Plain Old Data (POD). Las clases que no son POD no pueden usar listas de inicialización para la inicialización, incluidos los contenedores estándar de C++, como los vectores.
C++11 ha asociado el concepto de listas de inicialización y una clase de plantilla llamada std::initializer_list . Esto permitió que los constructores y otras funciones recibieran listas de inicialización como parámetros. Por ejemplo:
clase SequenceClass { público : SequenceClass ( std :: initializer_list < int > list ); };Esta descripción le permite crear una SequenceClass a partir de una secuencia de enteros de la siguiente manera:
SequenceClass someVar = { 1 , 4 , 5 , 6 };Esto demuestra cómo funciona un tipo especial de constructor para una lista de inicialización. Las clases que contienen dichos constructores se tratan de manera especial durante la inicialización (ver más abajo ).
La clase std::initializer_list<> está definida en la biblioteca estándar de C++11. Sin embargo, los objetos de esta clase solo pueden ser creados estáticamente por el compilador C++11 usando la sintaxis de corchetes {}. La lista se puede copiar después de la creación, sin embargo, esto será una copia por referencia. La lista de inicialización es constante: ni sus miembros ni sus datos se pueden cambiar después de la creación.
Debido a que std::initializer_list<> es un tipo completo, se puede usar en más que solo constructores. Las funciones ordinarias pueden tomar listas de inicialización escritas como argumento, por ejemplo:
void FunctionName ( std :: initializer_list < float > list ); Nombre de función ({ 1.0f , -3.45f , -0.4f });Los contenedores estándar se pueden inicializar así:
std :: vector < std :: string > v = { "xyzzy" , "plugh" , "abracadabra" }; std :: vector < std :: string > v { "xyzzy" , "plugh" , "abracadabra" }; Inicialización genéricaEl estándar C++ contiene una serie de problemas relacionados con la inicialización de tipos. Hay varias formas de inicializar tipos, y no todas conducen a los mismos resultados. Por ejemplo, la sintaxis tradicional de un constructor de inicialización puede parecer una declaración de función, y se debe tener especial cuidado para evitar que el compilador la analice incorrectamente. Solo los tipos agregados y los tipos POD se pueden inicializar con inicializadores agregados (del tipo SomeType var = {/*stuff*/};).
C ++ 11 proporciona una sintaxis que permite usar una forma única de inicialización para todo tipo de objetos al extender la sintaxis de la lista de inicialización:
struct EstructuraBásica { intx ; _ doble y ; }; estructura AltEstructura { AltStruct ( int x , doble y ) : x_ ( x ), y_ ( y ) {} privado : intx_ ; _ doble y_ ; }; EstructuraBásica var1 { 5 , 3.2 }; AltStruct var2 { 2 , 4.3 };La inicialización de var1 funciona exactamente igual que la inicialización de agregados, es decir, cada objeto se inicializará copiando el valor correspondiente de la lista de inicialización. Si es necesario, se aplicará la conversión de tipo implícita. Si la transformación deseada no existe, el código fuente se considerará no válido. Durante la inicialización de var2 , se llamará al constructor.
Es posible escribir código como este:
estructura IdString { std :: nombre de cadena ; _ identificador int ; }; IdCadena ObtenerCadena ( ) { return { "AlgúnNombre" , 4 }; // Tenga en cuenta la falta de tipos explícitos }La inicialización genérica no reemplaza completamente la sintaxis de inicialización del constructor. Si una clase tiene un constructor que toma una lista de inicialización ( TypeName(initializer_list<SomeType>); ) como argumento, tendrá prioridad sobre otras opciones de creación de objetos. Por ejemplo, en C++11 std::vector contiene un constructor que toma una lista de inicialización como argumento:
std :: vector < int > theVec { 4 };Este código dará como resultado una llamada de constructor que toma una lista de inicialización como argumento, en lugar de un constructor de un parámetro que crea un contenedor del tamaño dado. Para llamar a este constructor, el usuario deberá usar la sintaxis de invocación del constructor estándar.
Inferencia de tipoEn C++ estándar (y C), el tipo de una variable debe especificarse explícitamente. Sin embargo, con la llegada de los tipos de plantillas y las técnicas de metaprogramación de plantillas, el tipo de algunos valores, especialmente los valores devueltos por funciones, no se puede especificar fácilmente. Esto genera dificultades para almacenar datos intermedios en variables, a veces puede ser necesario conocer la estructura interna de una biblioteca de metaprogramación en particular.
C++11 ofrece dos formas de mitigar estos problemas. Primero, la definición de una variable explícitamente inicializable puede contener la palabra clave auto . Esto dará como resultado la creación de una variable del tipo del valor de inicialización:
auto someStrangeCallableType = std :: bind ( & SomeFunction , _2 , _1 , someObject ); auto otra variable = 5 ;El tipo someStrangeCallableType se convertirá en el tipo que devuelve la implementación concreta de la función de plantilla std::bindpara los argumentos proporcionados. El compilador determinará fácilmente este tipo durante el análisis semántico, pero el programador tendrá que investigar un poco para determinar el tipo.
El tipo otherVariable también está bien definido, pero el programador puede definirlo con la misma facilidad. Este tipo es int , lo mismo que una constante entera.
Además, la palabra clave decltype se puede usar para determinar el tipo de una expresión en tiempo de compilación . Por ejemplo:
int algoInt ; decltype ( someInt ) otherIntegerVariable = 5 ;Usar decltype es más útil junto con auto , ya que el tipo de una variable declarada como auto solo lo conoce el compilador. Además, el uso de decltype puede ser bastante útil en expresiones que utilizan la sobrecarga de operadores y la especialización de plantillas.
autotambién se puede utilizar para reducir la redundancia de código. Por ejemplo, en lugar de:
for ( vector < int >:: const_iterator itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )el programador puede escribir:
for ( auto itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )La diferencia se vuelve especialmente notable cuando un programador usa una gran cantidad de contenedores diferentes, aunque todavía hay una buena manera de reducir el código redundante: usar typedef.
Un tipo marcado con decltype puede ser diferente del tipo inferido con auto .
#incluir <vector> int principal () { const std :: vector < int > v ( 1 ); automático a = v [ 0 ]; // escribe a - int decltype ( v [ 0 ]) b = 1 ; // escriba b - const int& (valor devuelto // std::vector<int>::operator[](size_type) const) auto c = 0 ; // tipo c - int auto d = c ; // escriba d - int decltype ( c ) e ; // tipo e - int, tipo de entidad llamada c decltype (( c )) f = c ; // el tipo f es int& porque (c) es un lvalue decltype ( 0 ) g ; // tipo g es int ya que 0 es un valor r } For-loop a través de una colecciónEn C++ estándar , iterar sobre los elementos de una colección requiere mucho código . Algunos lenguajes, como C# , tienen funciones que proporcionan una instrucción " foreach " que recorre automáticamente los elementos de una colección de principio a fin. C++11 presenta una instalación similar. La instrucción for facilita la iteración sobre una colección de elementos:
int my_array [ 5 ] = { 1 , 2 , 3 , 4 , 5 }; para ( int & x : mi_matriz ) { x *= 2 ; }Esta forma de for, llamada "range-based for" en inglés, visitará cada elemento de la colección. Esto se aplicará a matrices C , listas de inicializadores y cualquier otro tipo que tenga funciones begin()y end()que devuelva iteradores . Todos los contenedores de la biblioteca estándar que tienen un par de inicio/fin funcionarán con una instrucción for en la colección.
Tal ciclo también funcionará, por ejemplo, con matrices tipo C, porque C++11 introduce artificialmente los pseudométodos necesarios para ellos (begin, end y algunos otros).
// recorrido basado en rangos de la matriz clásica int arr1 [] = { 1 , 2 , 3 }; para ( auto el : arr1 ); Funciones y expresiones lambdaEn C++ estándar, por ejemplo, cuando se usa la biblioteca de algoritmos de C++ estándar sort and find , a menudo existe la necesidad de definir funciones de predicado cerca de donde se llama al algoritmo. Solo hay un mecanismo en el lenguaje para esto: la capacidad de definir una clase de functor (está prohibido pasar una instancia de una clase definida dentro de una función a los algoritmos (Meyers, STL efectivo)). A menudo, este método es demasiado redundante y detallado, y solo dificulta la lectura del código. Además, las reglas estándar de C++ para clases definidas en funciones no permiten su uso en plantillas y, por lo tanto, las hacen imposibles de usar.
La solución obvia al problema era permitir la definición de expresiones lambda y funciones lambda en C++11. La función lambda se define así:
[]( int x , int y ) { devuelve x + y ; }El tipo de retorno de esta función sin nombre se calcula como decltype(x+y) . El tipo de valor devuelto solo se puede omitir si la función lambda tiene el formato . Esto limita el tamaño de la función lambda a una sola expresión. return expression
El tipo de devolución se puede especificar explícitamente, por ejemplo:
[]( int x , int y ) -> int { int z = x + y ; devuelve z ; }Este ejemplo crea una variable temporal z para almacenar un valor intermedio. Al igual que con las funciones normales, este valor intermedio no se conserva entre llamadas.
El tipo de devolución se puede omitir por completo si la función no devuelve un valor (es decir, el tipo de devolución es nulo )
También es posible utilizar referencias a variables definidas en el mismo ámbito que la función lambda. Un conjunto de tales variables generalmente se denomina cierre . Los cierres se definen y utilizan de la siguiente manera:
std :: vector < int > algunaLista ; entero total = 0 ; std :: for_each ( algunaLista . comenzar (), algunaLista . final (), [ & total ]( int x ) { total += x ; }); std :: cout << total ;Esto mostrará la suma de todos los elementos en la lista. La variable total se almacena como parte del cierre de la función lambda. Debido a que se refiere a la variable de pila total , puede cambiar su valor.
Las variables de cierre para las variables locales también se pueden definir sin usar el símbolo de referencia & , lo que significa que la función copiará el valor. Esto obliga al usuario a declarar su intención de hacer referencia o copiar una variable local.
Para las funciones lambda cuya ejecución está garantizada en su ámbito, es posible utilizar todas las variables de la pila sin necesidad de referencias explícitas a ellas:
std :: vector < int > algunaLista ; entero total = 0 ; std :: for_each ( algunaLista . comenzar (), algunaLista . fin (), [ & ]( int x ) { total += x ; });Los métodos de implementación pueden variar internamente, pero se espera que la función lambda almacene un puntero a la pila de la función en la que se creó, en lugar de operar en referencias de variables de pila individuales.
[&]Si se usa en su lugar [=], se copiarán todas las variables usadas, lo que permitirá que la función lambda se use fuera del alcance de las variables originales.
El método de transferencia predeterminado también se puede complementar con una lista de variables individuales. Por ejemplo, si necesita pasar la mayoría de las variables por referencia y una por valor, puede usar la siguiente construcción:
entero total = 0 ; valor int = 5 ; [ & , valor ]( int x ) { total += ( x * valor ); } ( 1 ); //(1) llamar a la función lambda con valor 1Esto hará que el total se pase por referencia y valor por valor.
Si una función lambda se define en un método de clase, se considera un amigo de esa clase. Tales funciones lambda pueden usar una referencia a un objeto del tipo de clase y acceder a sus campos internos:
[]( SomeType * typePtr ) { typePtr -> SomePrivateMemberFunction (); }Esto solo funcionará si el alcance de la función lambda es un método de clase SomeType .
El trabajo con el puntero this al objeto con el que interactúa el método actual se implementa de una manera especial. Debe estar marcado explícitamente en la función lambda:
[ esto ]() { esto -> SomePrivateMemberFunction (); }El uso de un formulario [&]o [=]una función lambda hace que esto esté disponible automáticamente.
El tipo de funciones lambda depende de la implementación; el nombre de este tipo está disponible solo para el compilador. Si necesita pasar una función lambda como parámetro, debe ser un tipo de plantilla o almacenarse mediante std::function . La palabra clave auto le permite guardar una función lambda localmente:
auto myLambdaFunc = [ this ]() { this -> SomePrivateMemberFunction (); };Además, si la función no acepta argumentos, ()puede omitir:
auto myLambdaFunc = []{ std :: cout << "hola" << std :: endl ; }; Sintaxis de funciones alternativasA veces es necesario implementar una plantilla de función que dé como resultado una expresión que tenga el mismo tipo y la misma categoría de valor que alguna otra expresión.
template < typename LHS , typename RHS > RETURN_TYPE AddingFunc ( const LHS & lhs , const RHS & rhs ) // ¿Qué debería ser RETURN_TYPE? { volver lhs + rhs ; }Para que la expresión AddingFunc(x, y) tenga el mismo tipo y la misma categoría de valor que la expresión lhs + rhs cuando se le den los argumentos x e y , se podría usar la siguiente definición dentro de C++11:
template < typename LHS , typename RHS > decltype ( std :: declval < const LHS &> () + std :: declval < const RHS &> ()) AddingFunc ( const LHS & lhs , const RHS & rhs ) { volver lhs + rhs ; }Esta notación es algo engorrosa, y sería bueno poder usar lhs y rhs en lugar de std::declval<const LHS &>() y std::declval<const RHS &>() respectivamente. Sin embargo, en la próxima versión
template < typename LHS , typename RHS > decltype ( lhs + rhs ) AddingFunc ( const LHS & lhs , const RHS & rhs ) // No válido en C++11 { volver lhs + rhs ; }más legible por humanos, los identificadores lhs y rhs utilizados en el operando decltype no pueden indicar opciones declaradas más tarde. Para resolver este problema, C++11 introduce una nueva sintaxis para declarar funciones con un tipo de retorno al final:
template < typename LHS , typename RHS > auto AddingFunc ( const LHS & lhs , const RHS & rhs ) -> decltype ( lhs + rhs ) { volver lhs + rhs ; }Cabe señalar, sin embargo, que en la implementación AddingFunc más genérica a continuación, la nueva sintaxis no se beneficia de la brevedad:
plantilla < nombre de tipo LHS , nombre de tipo RHS > Auto AddingFunc ( LHS && lhs , RHS && rhs ) - > decltype ( std :: adelante < LHS > ( lhs ) + std :: adelante < RHS > ( rhs )) { return std :: adelante < LHS > ( lhs ) + std :: adelante < RHS > ( rhs ); } plantilla < nombre de tipo LHS , nombre de tipo RHS > Auto AddingFunc ( LHS && lhs , RHS && rhs ) - > decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // mismo efecto que con std::forward arriba { return std :: adelante < LHS > ( lhs ) + std :: adelante < RHS > ( rhs ); } template < typename LHS , typename RHS > decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // mismo efecto que poner tipo al final AddingFunc ( LHS && lhs , RHS && rhs ) { return std :: adelante < LHS > ( lhs ) + std :: adelante < RHS > ( rhs ); }La nueva sintaxis se puede usar en declaraciones y declaraciones más simples:
estructura AlgunaEstructura { auto FuncName ( int x , int y ) -> int ; }; auto SomeStruct :: FuncName ( int x , int y ) -> int { devuelve x + y _ }El uso de la palabra clave " " autoen este caso significa solo una indicación tardía del tipo de retorno y no está relacionado con su inferencia automática.
Mejorando los constructores de objetosC++ estándar no permite llamar a un constructor de clase desde otro constructor de la misma clase; cada constructor debe inicializar por completo a todos los miembros de la clase o llamar a los métodos de la clase para hacerlo. Los miembros no constantes de una clase no se pueden inicializar en el lugar donde se declaran esos miembros.
C++11 se deshace de estos problemas.
El nuevo estándar permite llamar a un constructor de clase desde otro (la llamada delegación). Esto le permite escribir constructores que usan el comportamiento de otros constructores sin introducir código duplicado.
Ejemplo:
clase AlgunTipo { número entero ; público : SomeType ( int new_number ) : number ( new_number ) {} AlgunTipo () : AlgunTipo ( 42 ) {} };En el ejemplo, puede ver que el constructor SomeTypesin argumentos llama al constructor de la misma clase con un argumento entero para inicializar la variable number. Se podría lograr un efecto similar especificando un valor inicial de 42 para esta variable justo en su declaración.
clase AlgunTipo { numero entero = 42 ; público : AlgúnTipo () {} explícito SomeType ( int new_number ) : number ( new_number ) {} };Cualquier constructor de clase se inicializará numbera 42 si no le asigna un valor diferente.
Java , C# y D son ejemplos de lenguajes que también solucionan estos problemas .
Cabe señalar que si en C++03 se considera que un objeto está completamente creado cuando su constructor completa la ejecución, entonces en C++11, después de que se haya ejecutado al menos un constructor delegado, el resto de los constructores trabajarán en un objeto completamente construido. A pesar de esto, los objetos de la clase derivada solo se construirán después de que se hayan ejecutado todos los constructores de las clases base.
Sustitución explícita de funciones virtuales y finalidadEs posible que la firma de un método virtual se haya cambiado en la clase base o se haya configurado incorrectamente en la clase derivada inicialmente. En tales casos, el método dado en la clase derivada no anulará el método correspondiente en la clase base. Entonces, si el programador no cambia correctamente la firma del método en todas las clases derivadas, es posible que el método no se llame correctamente durante la ejecución del programa. Por ejemplo:
base de estructura { vacío virtual some_func (); }; estructura derivada : base { vacío sone_func (); };Aquí, el nombre de una función virtual declarada en una clase derivada está mal escrito, por lo que dicha función no se anulará Base::some_funcy, por lo tanto, no se llamará polimórficamente a través de un puntero o referencia al subobjeto base.
C ++ 11 agregará la capacidad de rastrear estos problemas en tiempo de compilación (en lugar de tiempo de ejecución). Para compatibilidad con versiones anteriores, esta característica es opcional. La nueva sintaxis se muestra a continuación:
estructura B { vacío virtual some_func (); vacío virtual f ( int ); vacío virtual g () const ; }; estructura D1 : pública B { anular sone_func () anular ; // error: nombre de función no válido void f ( int ) override ; // OK: anula la misma función en la clase base virtual void f ( long ) override ; // error: el tipo de parámetro no coincide virtual void f ( int ) const override ; // error: función cv-calificación no coincide virtual int f ( int ) override ; // error: el tipo de retorno no coincide virtual void g () const final ; // OK: anula la misma función en la clase base virtual void g ( long ); // OK: nueva función virtual }; estructura D2 : D1 { vacío virtual g () const ; // error: intento de reemplazar la función final };La presencia de un especificador para una función virtual finalsignifica que su reemplazo posterior es imposible. Además, una clase definida con el especificador final no se puede usar como clase base:
estructura F final { int x , y ; }; struct D : F // error: herencia de clases finales no permitida { intz ; _ };Los identificadores overridey finaltienen un significado especial solo cuando se usan en ciertas situaciones. En otros casos, pueden usarse como identificadores normales (por ejemplo, como el nombre de una variable o función).
Constante de puntero nuloDesde el advenimiento de C en 1972, la constante 0 ha desempeñado el doble papel de un número entero y un puntero nulo. Una forma de lidiar con esta ambigüedad inherente al lenguaje C es la macro NULL, que normalmente realiza la sustitución ((void*)0)o 0. C++ difiere de C en este aspecto, ya que solo permite el uso 0de un puntero nulo como constante. Esto conduce a una mala interacción con la sobrecarga de funciones:
foo vacío ( char * ); foo vacío ( int );Si la macro NULLse define como 0(que es común en C++), la línea foo(NULL);resultará en una llamada foo(int), no foo(char *)como podría sugerir una mirada rápida al código, que casi con seguridad no es lo que pretendía el programador.
Una de las novedades de C++11 es una nueva palabra clave para describir una constante de puntero nulo - nullptr. Esta constante es de tipo std::nullptr_t, que puede convertirse implícitamente al tipo de cualquier puntero y compararse con cualquier puntero. No se permite la conversión implícita a un tipo integral, excepto para bool. La propuesta original del estándar no permitía la conversión implícita a booleanos, pero el grupo de redacción del estándar permitió tales conversiones en aras de la compatibilidad con los tipos de punteros convencionales. La redacción propuesta se modificó tras una votación unánime en junio de 2008 [1] .
Para compatibilidad con versiones anteriores, 0también se puede usar una constante como un puntero nulo.
char * pc = nullptr ; // verdadero int * pi = nullptr ; // verdadero bool b = nullptr ; // Correcto. b=falso. int i = nullptr ; // error foo ( nullptr ); // llama a foo(char *), no a foo(int);A menudo, las construcciones en las que se garantiza que el puntero estará vacío son más simples y seguras que el resto, por lo que puede sobrecargarse con . nullptr_t
clase Carga útil ; clase SmartPtr { SmartPtr () = predeterminado ; SmartPtr ( nullptr_t ) {} // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< SmartPtr explícito ( Carga útil * aData ) : fData ( aDatos ) {} // copiar constructores y op= omitir ~ SmartPtr () { delete fData ; } privado : Carga útil * fData = nullptr ; } SmartPtr getPayload1 () { return nullptr ; } // Se llamará a la sobrecarga de SmartPtr(nullptr_t). Enumeraciones fuertemente tipadasEn C++ estándar, las enumeraciones no son seguras. De hecho, están representados por números enteros, a pesar de que los tipos de enumeraciones en sí son diferentes entre sí. Esto permite realizar comparaciones entre dos valores de diferentes enumeraciones. La única opción que ofrece C++03 para proteger las enumeraciones es no convertir implícitamente enteros o elementos de una enumeración en elementos de otra enumeración. Además, la forma en que se representa en la memoria (tipo entero) depende de la implementación y, por lo tanto, no es portátil. Finalmente, los elementos de enumeración tienen un alcance común, lo que hace imposible crear elementos con el mismo nombre en diferentes enumeraciones.
C++11 ofrece una clasificación especial de estas enumeraciones, libre de las desventajas anteriores. Para describir tales enumeraciones, se usa una declaración enum class(también se puede usar enum structcomo sinónimo):
enumeración de clase enum { Val1 , Val2 , Val3 = 100 , Val4 , /* = 101 */ };Tal enumeración es de tipo seguro. Los elementos de una enumeración de clase no se pueden convertir implícitamente en enteros. Como consecuencia, la comparación con números enteros también es imposible (la expresión Enumeration::Val4 == 101da como resultado un error de compilación).
El tipo de enumeración de clase ahora es independiente de la implementación. De forma predeterminada, como en el caso anterior, este tipo es int, pero en otros casos, el tipo se puede configurar manualmente de la siguiente manera:
clase de enumeración Enum2 : int sin firmar { Val1 , Val2 };El alcance de los miembros de la enumeración está determinado por el alcance del nombre de la enumeración. El uso de nombres de elementos requiere especificar el nombre de la enumeración de clase. Entonces, por ejemplo, el valor Enum2::Val1está definido, pero el valor Val1 no está definido.
Además, C ++ 11 ofrece la posibilidad de tipos subyacentes y de alcance explícitos para enumeraciones regulares:
enum Enum3 : largo sin firmar { Val1 = 1 , Val2 };En este ejemplo, los nombres de los elementos de enumeración se definen en el espacio de enumeración (Enum3::Val1), pero por compatibilidad con versiones anteriores, los nombres de los elementos también están disponibles en el ámbito común.
También en C++ 11 es posible predeclarar enumeraciones. En versiones anteriores de C++, esto no era posible porque el tamaño de una enumeración dependía de sus elementos. Tales declaraciones solo se pueden usar cuando se especifica el tamaño de la enumeración (explícita o implícitamente):
enumeración Enum1 ; // no válido para C++ y C++11; el tipo subyacente no se puede determinar enum Enum2 : unsigned int ; // verdadero para C++11, tipo subyacente explícitamente especificado enum class Enum3 ; // verdadero para C++11, el tipo subyacente es int enum class Enum4 : unsigned int ; // verdadero para C++11. enum Enum2 : corto sin firmar ; // no válido para C++11 porque Enum2 se declaró previamente con un tipo subyacente diferente Paréntesis angularesLos analizadores estándar de C++ siempre definen la combinación de caracteres ">>" como el operador de desplazamiento a la derecha. La ausencia de un espacio entre los corchetes angulares de cierre en los parámetros de la plantilla (si están anidados) se trata como un error de sintaxis.
C++11 mejora el comportamiento del analizador en este caso, de modo que varios corchetes angulares rectos se interpretarán como listas de argumentos de plantilla de cierre.
El comportamiento descrito se puede arreglar a favor del enfoque antiguo usando paréntesis.
plantilla < clase T > clase Y { /* ... */ }; Y < X < 1 >> x3 ; // Correcto, igual que "Y<X<1> > x3;". Y < X < 6 >> 1 >> x4 ; // Error de sintaxis. Debe escribir "Y<X<(6>>1)>> x4;".Como se muestra arriba, este cambio no es totalmente compatible con el estándar anterior.
Operadores de conversión explícitosEl estándar de C++ proporciona la palabra clave explicitcomo modificador para los constructores de un parámetro para que dichos constructores no funcionen como constructores de conversión implícita. Sin embargo, esto no afecta de ninguna manera a los operadores de conversión reales. Por ejemplo, una clase de puntero inteligente podría contener operator bool()para imitar un puntero normal. Dicho operador se puede llamar, por ejemplo, así: if(smart_ptr_variable)(la rama se ejecuta si el puntero no es nulo). El problema es que dicho operador no protege contra otras conversiones inesperadas. Dado que el tipo boolse declara como un tipo aritmético en C++, es posible la conversión implícita a cualquier tipo entero o incluso a un tipo de punto flotante, lo que a su vez puede conducir a operaciones matemáticas inesperadas.
En C++11, la palabra clave explicittambién se aplica a los operadores de conversión. Al igual que los constructores, protege contra conversiones implícitas inesperadas. Sin embargo, las situaciones en las que el lenguaje espera contextualmente un tipo booleano (por ejemplo, en expresiones condicionales, bucles y operandos de operadores lógicos) se consideran conversiones explícitas y el operador de conversión booleano explícito se invoca directamente.