Cplusplus Es PDF
Cplusplus Es PDF
#c++
Tabla de contenido
Acerca de 1
Observaciones 2
Versiones 2
Examples 2
Hola Mundo 2
Análisis 2
Comentarios 4
Función 6
Declaración de funciones 6
Llamada de función 7
Definición de la función 8
Sobrecarga de funciones 8
Parámetros predeterminados 8
Preprocesador 12
Introducción 14
Examples 14
Capítulo 3: Alcances 17
Examples 17
Alcance de bloque simple 17
Variables globales 17
Examples 19
std :: for_each 19
std :: next_permutation 19
std :: acumular 20
std :: encontrar 22
std :: cuenta 23
std :: count_if 24
std :: find_if 26
std :: min_element 27
Capítulo 5: Alineación 30
Introducción 30
Observaciones 30
Examples 30
Controlando la alineación 31
Capítulo 6: Archivo I / O 32
Introducción 32
Examples 32
Abriendo un archivo 32
Leyendo de un archivo 33
Escribiendo en un archivo 35
Modos de apertura 36
Cerrando un archivo 37
Flushing un arroyo 38
Copiando un archivo 41
¿Revisar el final del archivo dentro de una condición de bucle, mala práctica? 42
Observaciones 45
Examples 45
Ejemplo básico 45
Archivos fuente 45
El proceso de compilación 47
Examples 49
Capítulo 9: Arrays 51
Introducción 51
Examples 51
Una matriz de matriz sin formato de tamaño fijo (es decir, una matriz sin formato 2D). 54
Inicialización de matriz 57
Sintaxis 59
Examples 59
[[sin retorno]] 59
[[caer a través]] 60
[[nodiscard]] 62
[[maybe_unused]] 62
Observaciones 64
Examples 64
Muestra auto básica 64
Introducción 68
Sintaxis 68
Observaciones 68
Examples 68
En bucle 71
Mientras bucle 73
Bucle Do-while 75
Examples 79
Observaciones 81
Examples 81
Corrientes de cuerda 81
Copiando arroyos 83
Arrays 84
Imprimiendo colecciones con iostream 84
Impresión básica 84
Generación y transformación. 85
Arrays 85
Análisis de archivos 86
Transformación 87
Introducción 88
Observaciones 88
Examples 89
Declaración y uso 89
Examples 91
prvalue 91
xvalor 92
valor 92
glvalue 93
valor 93
Sintaxis 95
Observaciones 95
Examples 95
Especificadores de acceso 96
Herencia 97
Herencia virtual 99
Fondo 103
Amistad 105
Observaciones 123
Examples 123
Capítulo 19: Comparaciones lado a lado de ejemplos clásicos de C ++ resueltos a través de 130
Examples 130
Introducción 132
Observaciones 132
Examples 132
Examples 155
Introducción 163
Observaciones 163
Examples 164
No hay declaración de retorno para una función con un tipo de retorno no nulo 164
Eliminar un objeto derivado a través de un puntero a una clase base que no tiene un destru 171
Observaciones 178
Examples 178
Introducción 184
Observaciones 184
Examples 184
OpenMP: Secciones paralelas 184
Sintaxis 188
Observaciones 188
Examples 188
Introducción 198
Observaciones 198
Examples 198
Introducción 204
Observaciones 204
Examples 204
Introducción 205
Documentación 207
Construyendo con scons 208
Ninja 208
Introducción 208
Introducción 209
Introducción 209
Introducción 211
Examples 211
Observaciones 214
Examples 214
caso 214
cambiar 214
captura 215
defecto 215
Si 216
más 216
ir 216
regreso 217
lanzar 217
tratar 218
Introducción 224
Sintaxis 224
Observaciones 224
Examples 225
nulo * a T * 230
Examples 232
Sintaxis 237
Parámetros 237
Observaciones 237
Examples 237
Introducción 241
Examples 241
Observaciones 243
Examples 243
Introducción 247
Examples 247
Observaciones 255
Examples 255
Arrays 259
Examples 260
Introducción 265
Observaciones 265
Examples 266
C ++ 11 266
Plantillas 267
Concurrencia 267
General 268
Concurrencia 268
C ++ 14 269
C ++ 17 269
C ++ 03 270
C ++ 98 270
C ++ 20 271
Examples 272
Uso de este puntero para diferenciar entre datos de miembros y parámetros 275
Sintaxis 281
Parámetros 281
Observaciones 281
Examples 281
Observaciones 294
Examples 294
Examples 296
Examples 301
Variables 301
Funciones 301
Examples 304
clase 304
estructura 305
enumerar 305
Unión 307
Introducción 308
Sintaxis 308
Observaciones 308
Examples 309
Introducción 319
Sintaxis 319
Observaciones 319
Examples 319
Introducción 321
Observaciones 321
Examples 321
mudable 321
registro 322
estático 322
auto 323
externo 324
Examples 326
Introducción 329
Examples 329
Examples 332
Mejores prácticas: tirar por valor, atrapar por referencia constante 335
Observaciones 343
Examples 343
Introducción 346
Sintaxis 346
Parámetros 346
Examples 347
Cuantificadores 349
Anclas 351
Examples 352
Capítulo 54: Función de C ++ "llamada por valor" vs. "llamada por referencia" 354
Introducción 354
Examples 354
Llamar por valor 354
Observaciones 356
Examples 356
Observaciones 358
Examples 358
Sintaxis 360
Observaciones 360
Examples 360
Introducción 367
Sintaxis 367
Observaciones 367
Examples 368
Sintaxis 378
Observaciones 378
Examples 378
Encapsulacion 379
Introducción 387
Examples 387
Observaciones 392
Examples 392
Sintaxis 395
Observaciones 395
Examples 395
Apilar 395
Introducción 400
Observaciones 400
Examples 400
Conclusión 405
Conclusión 407
Conclusión 408
Observaciones 409
Examples 409
Observaciones 411
Examples 411
Introducción 421
Examples 421
Introducción 422
Observaciones 422
Examples 422
Observaciones 424
Examples 424
Examples 426
descanso 426
continuar 426
hacer 426
para 426
mientras 427
Examples 428
Rompiendolo 428
Examples 437
Sintaxis 443
Parámetros 443
Observaciones 444
Examples 444
Introducción 461
Examples 461
cierto 461
falso 461
nullptr 461
esta 462
Examples 465
Observaciones 469
Examples 469
Introducción 475
Observaciones 475
Examples 476
Examples 486
Introducción 487
Examples 487
Introducción 489
Observaciones 489
Examples 489
Introducción 500
Examples 500
Observaciones 502
Examples 504
Examples 508
Examples 516
Observaciones 517
std :: shared_mutex procesó lectura / escritura más de 2 veces más que std :: shared_timed 522
Examples 525
Estrategias para clases de bloqueo: std :: try_to_lock, std :: adopt_lock, std :: defer_lo 526
Introducción 529
Observaciones 529
Examples 529
Punteros a funciones 529
Observaciones 531
Examples 531
Examples 537
Ejemplo 540
Introducción 543
Examples 543
Sintaxis 546
Observaciones 546
Examples 546
Examples 550
Introducción 552
Sintaxis 552
Observaciones 552
Examples 554
asm 554
explícito 555
noexcept 555
tamaño de 557
Examples 563
const 563
decltype 563
firmado 564
no firmado 564
volátil 565
Examples 566
En t 566
bool 566
carbonizarse 566
char16_t 566
char32_t 567
flotador 567
doble 567
largo 567
corto 568
vacío 568
wchar_t 568
Examples 570
Observaciones 571
Examples 571
Subclases 572
Introducción 575
Examples 575
Examples 579
Introducción 584
Sintaxis 584
Observaciones 584
Examples 586
Examples 600
Archivo vec.hh: wrapper para std :: vector, utilizado para mostrar el registro cuando se l 602
Examples 614
Observaciones 618
Examples 618
Introducción 622
Observaciones 622
Examples 622
Macros 625
Macros x 631
Introducción 635
Examples 635
Captura 635
Introducción 637
Sintaxis 637
Observaciones 637
Examples 637
Sintaxis 643
Examples 643
Sintaxis 647
Observaciones 647
Examples 647
Uso de eliminaciones personalizadas para crear una envoltura para una interfaz C 654
Observaciones 661
Examples 661
Cierre 661
Examples 667
Uso de la recursión de la cola y la recursión del estilo de Fibonnaci para resolver la sec 667
Observaciones 669
Examples 669
Examples 671
Examples 673
Observaciones 676
Examples 676
Examples 685
dynamic_cast 685
Introducción 687
Examples 687
Semáforo C ++ 11 687
Examples 689
Examples 690
enable_if 690
void_t 692
is_detected 697
Introducción 700
Observaciones 700
Examples 700
Introducción 705
Observaciones 705
Examples 705
Sintaxis 721
Parámetros 721
Observaciones 721
Examples 721
static_assert 721
Parámetros 723
Observaciones 723
Examples 723
Examples 728
Observaciones 731
Examples 731
Observaciones 732
Examples 732
Ejemplo 732
Métodos 733
Capítulo 125: std :: function: Para envolver cualquier elemento que sea llamable 735
Examples 735
Introducción 742
Examples 742
Examples 745
Observaciones 749
Examples 749
Multi-Mapa 756
Creando std :: map con tipos definidos por el usuario como clave 756
Examples 758
Introducción 758
valor_o 761
Examples 762
Introducción 764
Observaciones 764
Examples 764
Introducción 771
Sintaxis 771
Observaciones 772
Examples 772
Terrible 772
Concatenación 774
en (n) 775
frente() 775
atrás() 776
Tokenizar 776
Observaciones 788
Examples 788
Introducción 791
Observaciones 791
Examples 791
Iteradores 799
Introducción 816
Examples 816
Ir a la limpieza 818
Introducción 820
Examples 820
Observaciones 834
Examples 834
1. Ejemplo de base sin devoluciones covariantes, muestra por qué son deseables 834
2. Versión de resultado covariante del ejemplo base, comprobación de tipos estática. 835
3. Resultado del puntero inteligente covariante (limpieza automatizada). 836
Observaciones 838
Examples 838
Constantes 838
Funciones 838
Sintaxis 843
Observaciones 843
Examples 843
Sintaxis 845
Observaciones 845
Examples 845
Examples 847
Introducción 849
Sintaxis 849
Examples 849
Observaciones 852
Examples 852
Introducción 854
Observaciones 854
Examples 854
Introducción 856
Sintaxis 856
Observaciones 856
Examples 856
Volver a declarar miembros de una clase base para evitar ocultar el nombre 856
Examples 858
Definiciones 860
Introducción 862
Examples 862
Definición de un miembro de datos estáticos en la definición de clase 862
Creditos 863
Acerca de
You can share this PDF with anyone you feel could benefit from it, downloaded the latest version
from: cplusplus
It is an unofficial and free C++ ebook created for educational purposes. All the content is extracted
from Stack Overflow Documentation, which is written by many hardworking individuals at Stack
Overflow. It is neither affiliated with Stack Overflow nor official C++.
The content is released under Creative Commons BY-SA, and the list of contributors to each
chapter are provided in the credits section at the end of this book. Images may be copyright of
their respective owners unless otherwise specified. All trademarks and registered trademarks are
the property of their respective company owners.
Use the content presented in this book at your own risk; it is not guaranteed to be correct nor
accurate, please send your feedback and corrections to [email protected]
https://riptutorial.com/es/home 1
Capítulo 1: Empezando con C ++
Observaciones
El programa 'Hello World' es un ejemplo común que se puede usar simplemente para verificar la
presencia del compilador y la biblioteca. Utiliza la biblioteca estándar de C ++, con std::cout de
<iostream> , y solo tiene que compilar un archivo, lo que minimiza la posibilidad de un error de
usuario durante la compilación.
Versiones
C ++ 17 TBD 2017-01-01
C ++ 20 TBD 2020-01-01
Examples
Hola Mundo
#include <iostream>
int main()
{
std::cout << "Hello World!" << std::endl;
}
https://riptutorial.com/es/home 2
Análisis
Examinemos cada parte de este código en detalle:
Los flujos de entrada / salida (E / S) estándar proporcionan formas para que los
programas obtengan entrada y salgan a un sistema externo, generalmente el terminal.
• int main() { ... } define una nueva función llamada main . Por convención, la función main
se llama a la ejecución del programa. Sólo debe haber una función main en un programa de
C ++, y siempre debe devolver un número del tipo int .
Aquí, el int es lo que se llama el tipo de retorno de la función. El valor devuelto por la
función main es un código de salida.
Por convención, un sistema que ejecuta el programa interpreta como exitoso un código de
salida del programa 0 o EXIT_SUCCESS . Cualquier otro código de retorno está asociado con un
error.
Si no hay ninguna declaración de return , la función main (y, por lo tanto, el propio programa)
devuelve 0 de forma predeterminada. En este ejemplo, no necesitamos escribir
explícitamente la return 0; .
Todas las demás funciones, excepto aquellas que devuelven el tipo void , deben devolver
explícitamente un valor de acuerdo con su tipo de retorno, o de lo contrario no deben
devolverlo en absoluto.
• std::cout << "Hello World!" << std::endl; grabados "¡Hola mundo!" al flujo de salida
estándar:
Hay muchos espacios de nombres. Aquí, usamos :: para mostrar que queremos usar
cout desde el std nombres std . Para obtener más información, consulte Operador de
resolución de alcance - Documentación de Microsoft .
○ << es, en este contexto , el operador de inserción de flujo , llamado así porque
inserta un objeto en el objeto de flujo .
https://riptutorial.com/es/home 3
La biblioteca estándar define el operador << para realizar la inserción de datos para
ciertos tipos de datos en flujos de salida. stream << content inserta el content en el flujo
y devuelve lo mismo, pero el flujo actualizado. Esto permite encadenar inserciones de
secuencias: std::cout << "Foo" << " Bar"; Imprime "FooBar" en la consola.
El manipulador de flujo std::endl hace dos cosas: primero inserta el carácter de fin de
línea y luego vacía el búfer del flujo para forzar que el texto aparezca en la consola.
Esto asegura que los datos insertados en la transmisión realmente aparezcan en su
consola. (Los datos de transmisión generalmente se almacenan en un búfer y luego se
"descargan" en lotes, a menos que se fuerce un vaciado de inmediato).
Comentarios
Un comentario es una forma de colocar texto arbitrario dentro del código fuente sin que el
compilador de C ++ lo interprete con un significado funcional. Los comentarios se utilizan para dar
una idea del diseño o método de un programa.
int main()
{
// This is a single-line comment.
int a; // this also is a single-line comment
int i; // this is another single-line comment
}
https://riptutorial.com/es/home 4
C-Style / Block Comentarios
La secuencia /* se usa para declarar el inicio del bloque de comentarios y la secuencia */ se usa
para declarar el final del comentario. Todo el texto entre las secuencias de inicio y finalización se
interpreta como un comentario, incluso si el texto es de otro modo una sintaxis de C ++ válida.
Estos a veces se denominan comentarios de "estilo C", ya que esta sintaxis de comentario se
hereda del lenguaje predecesor de C ++, C:
int main()
{
/*
* This is a block comment.
*/
int a;
}
int main()
{
/* A block comment with the symbol /*
Note that the compiler is not affected by the second /*
however, once the end-block-comment symbol is reached,
the comment ends.
*/
int a;
}
El ejemplo anterior es un código válido de C ++ (y C). Sin embargo, tener /* adicional dentro de
un comentario de bloque puede dar como resultado una advertencia en algunos compiladores.
Los comentarios en bloque también pueden comenzar y terminar dentro de una sola línea. Por
ejemplo:
https://riptutorial.com/es/home 5
Sin embargo, los comentarios también tienen sus desventajas:
Del mismo modo, mantener las versiones antiguas de un fragmento de código en un comentario
con fines de referencia es desagradable, ya que desordena los archivos al tiempo que ofrece
poco valor en comparación con la exploración del historial del código a través de un sistema de
versiones.
Función
Una función es una unidad de código que representa una secuencia de sentencias.
Las funciones pueden aceptar argumentos o valores y devolver un solo valor (o no). Para usar
una función, una llamada de función se usa en valores de argumento y el uso de la llamada de
función se reemplaza con su valor de retorno.
Cada función tiene una firma de tipo : los tipos de sus argumentos y el tipo de su tipo de retorno.
Las funciones están inspiradas en los conceptos del procedimiento y la función matemática.
Las funciones a menudo están destinadas a realizar una tarea específica. y puede ser llamado
desde otras partes de un programa. Una función debe ser declarada y definida antes de ser
llamada en otro lugar en un programa.
• Nota: las definiciones de funciones populares pueden estar ocultas en otros archivos
incluidos (a menudo por conveniencia y reutilización en muchos archivos). Este es un uso
común de los archivos de encabezado.
https://riptutorial.com/es/home 6
Declaración de funciones
Una declaración de función declara la existencia de una función con su nombre y tipo de firma
para el compilador. La sintaxis es la siguiente:
int add2(int i); // The function is of the type (int) -> (int)
El nombre del argumento es opcional; La declaración para la función también podría ser la
siguiente:
Según la regla de una definición , una función con un cierto tipo de firma solo se puede declarar
o definir una vez en una base de código C ++ completa visible para el compilador de C ++. En
otras palabras, las funciones con una firma de tipo específica no se pueden redefinir, solo deben
definirse una vez. Por lo tanto, lo siguiente no es válido en C ++:
int add2(int i); // The compiler will note that add2 is a function (int) -> int
int add2(int j); // As add2 already has a definition of (int) -> int, the compiler
// will regard this as an error.
Si una función no devuelve nada, su tipo de retorno se escribe como void . Si no toma
parámetros, la lista de parámetros debe estar vacía.
void do_something(); // The function takes no parameters, and does not return anything.
// Note that it can still affect variables it has access to.
Llamada de función
Una función puede ser llamada después de que haya sido declarada. Por ejemplo, el siguiente
programa llama a add2 con el valor de 2 dentro de la función de main :
#include <iostream>
https://riptutorial.com/es/home 7
// add2's definition may be LINKED in from another object file.
int main()
{
std::cout << add2(2) << "\n"; // add2(2) will be evaluated at this point,
// and the result is printed.
return 0;
}
Definición de la función
Una definición de función * es similar a una declaración, excepto que también contiene el código
que se ejecuta cuando se llama a la función dentro de su cuerpo.
int add2(int i) // Data that is passed into (int i) will be referred to by the name i
{ // while in the function's curly brackets or "scope."
Sobrecarga de funciones
Puedes crear múltiples funciones con el mismo nombre pero diferentes parámetros.
int add2(int i, int j) // However, when add2() is called with two parameters, the
{ // code from the initial declaration will be overloaded,
int k = i + j + 2 ; // and the code in this declaration will be evaluated
return k; // instead.
}
Ambas funciones se llaman con el mismo nombre add2 , pero la función real que se llama
depende directamente de la cantidad y el tipo de los parámetros en la llamada. En la mayoría de
los casos, el compilador de C ++ puede calcular qué función llamar. En algunos casos, el tipo
debe ser explícitamente establecido.
Parámetros predeterminados
https://riptutorial.com/es/home 8
Los valores predeterminados para los parámetros de función solo se pueden especificar en las
declaraciones de función.
En este ejemplo, se puede llamar a multiply() con uno o dos parámetros. Si solo se proporciona
un parámetro, b tendrá un valor predeterminado de 7. Los argumentos predeterminados se deben
colocar en los últimos argumentos de la función. Por ejemplo:
Ciertas secuencias de caracteres especiales que se reducirán a las llamadas de función del
compilador, como ! , + , - , * , % y << y muchos más. Estos caracteres especiales se asocian
normalmente con el uso no programado o se usan para la estética (por ejemplo, el carácter + se
reconoce comúnmente como el símbolo de adición tanto en la programación en C ++ como en
matemáticas elementales).
C ++ maneja estas secuencias de caracteres con una sintaxis especial; pero, en esencia, cada
aparición de un operador se reduce a una llamada de función. Por ejemplo, la siguiente expresión
en C ++:
3+3
operator+(3, 3)
https://riptutorial.com/es/home 9
Visibilidad de prototipos y declaraciones de funciones.
En C ++, el código debe ser declarado o definido antes de su uso. Por ejemplo, lo siguiente
produce un error de tiempo de compilación:
int main()
{
foo(2); // error: foo is called, but has not yet been declared
}
Hay dos formas de resolver esto: poner la definición o la declaración de foo() antes de su uso en
main() . Aquí hay un ejemplo:
int main()
{
foo(2); // OK: foo is completely defined beforehand, so it can be called here.
}
Sin embargo, también es posible "declarar hacia adelante" la función poniendo solo una
declaración "prototipo" antes de su uso y luego definiendo el cuerpo de la función más adelante:
El prototipo debe especificar el tipo de retorno ( void ), el nombre de la función ( foo ) y los tipos
de variables de la lista de argumentos ( int ), pero los nombres de los argumentos NO son
necesarios .
Una forma común de integrar esto en la organización de los archivos de origen es hacer un
archivo de encabezado que contenga todas las declaraciones de prototipo:
// foo.h
void foo(int); // prototype declaration
https://riptutorial.com/es/home 10
#include "foo.h" // foo's prototype declaration is "hidden" in here
void foo(int x) { } // foo's body definition
y luego, una vez compilado, vincule el archivo de objeto correspondiente foo.o al archivo de
objeto compilado donde se usa en la fase de enlace, main.o :
• Nota: algunos códigos compilados están vinculados entre sí, pero no para crear un
programa final. Por lo general, este código "vinculado" también se puede empaquetar en un
formato que puede ser usado por otros programas. Este "paquete de código empaquetado y
utilizable" es lo que los programadores de C ++ denominan una biblioteca.
Muchos compiladores de C ++ también pueden combinar o deshacer ciertas partes del proceso
de compilación para facilitar o para un análisis adicional. Muchos programadores de C ++ usarán
diferentes herramientas, pero todas las herramientas generalmente seguirán este proceso
generalizado cuando estén involucrados en la producción de un programa.
https://riptutorial.com/es/home 11
El siguiente enlace extiende esta discusión y proporciona un bonito gráfico para ayudar. [1]:
http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html
Preprocesador
Edita el código fuente, recorta algunos bits, cambia otros y agrega otras cosas.
En los archivos de origen, podemos incluir directivas de preprocesador. Estas directivas le indican
al preprocesador que realice acciones específicas. Una directiva comienza con un # en una nueva
línea. Ejemplo:
#define ZERO 0
#include <something>
directiva. Lo que hace es toma todos something y lo inserta en el archivo de donde estaba la
directiva. El programa hola mundo comienza con la línea.
#include <iostream>
Esta línea agrega las funciones y objetos que le permiten usar la entrada y salida estándar.
El lenguaje C, que también utiliza el preprocesador, no tiene tantos archivos de encabezado como
el lenguaje C ++, pero en C ++ puede usar todos los archivos de encabezado C.
directiva. Esto le dice al preprocesador que a medida que avanza en el archivo, debe reemplazar
cada ocurrencia de something con something_else . También puede hacer cosas similares a las
funciones, pero eso probablemente cuenta como C ++ avanzado.
El something_else no es necesario, pero si se define something como nada, entonces fuera de las
directivas de preprocesador, todas las apariciones de something se desvanecerá.
En realidad, esto es útil, debido a las #if , #else y #ifdef directivas. El formato para estos sería el
siguiente:
#if something==true
//code
#else
//more code
#endif
https://riptutorial.com/es/home 12
#ifdef thing_that_you_want_to_know_if_is_defined
//code
#endif
Estas directivas insertan el código que está en el bit verdadero y borran los bits falsos. Esto se
puede usar para tener bits de código que solo se incluyen en ciertos sistemas operativos, sin
tener que volver a escribir todo el código.
https://riptutorial.com/es/home 13
Capítulo 2: Administracion de recursos
Introducción
Una de las cosas más difíciles de hacer en C y C ++ es la administración de recursos.
Afortunadamente, en C ++, tenemos muchas maneras de diseñar el manejo de recursos en
nuestros programas. Este artículo espera explicar algunos de los modismos y métodos utilizados
para administrar los recursos asignados.
Examples
Adquisición de recursos es la inicialización
#include <memory>
#include <iostream>
using namespace std;
int main() {
{
auto_ptr ap(new int(5)); // dynamic memory is the resource
cout << *ap << endl; // prints 5
} // auto_ptr is destroyed, its resource is automatically freed
}
C ++ 11
#include <memory>
#include <iostream>
using namespace std;
int main() {
auto_ptr ap1(new int(5));
cout << *ap1 << endl; // prints 5
auto_ptr ap2(ap1); // copy ap2 from ap1; ownership now transfers to ap2
cout << *ap2 << endl; // prints 5
cout << ap1 == nullptr << endl; // prints 1; ap1 has lost ownership of resource
}
https://riptutorial.com/es/home 14
entre otras cosas. La razón por la que hace esto es para evitar que se borre la memoria dos
veces: si hay dos auto_ptrs con propiedad del mismo recurso, ambos intentan liberarla cuando se
destruyen. La liberación de un recurso ya liberado generalmente puede causar problemas, por lo
que es importante prevenirlo. Sin embargo, std::shared_ptr tiene un método para evitar esto y no
transfiere la propiedad al copiar:
#include <memory>
#include <iostream>
using namespace std;
int main() {
shared_ptr sp2;
{
shared_ptr sp1(new int(5)); // give ownership to sp1
cout << *sp1 << endl; // prints 5
sp2 = sp1; // copy sp2 from sp1; both have ownership of resource
cout << *sp1 << endl; // prints 5
cout << *sp2 << endl; // prints 5
} // sp1 goes out of scope and is destroyed; sp2 has sole ownership of resource
cout << *sp2 << endl;
} // sp2 goes out of scope; nothing has ownership, so resource is freed
Pueden surgir problemas cuando varios subprocesos intentan acceder a un recurso. Para un
ejemplo simple, supongamos que tenemos un hilo que agrega uno a una variable. Para ello,
primero lee la variable, le agrega una y luego la almacena de nuevo. Supongamos que
inicializamos esta variable a 1, luego creamos dos instancias de este hilo. Una vez que ambos
subprocesos terminan, la intuición sugiere que esta variable debería tener un valor de 3. Sin
embargo, la siguiente tabla ilustra lo que podría salir mal:
Hilo 1 Hilo 2
Como puede ver, al final de la operación, 2 está en la variable, en lugar de 3. La razón es que el
Subproceso 2 leyó la variable antes de que el Subproceso 1 terminara de actualizarla. ¿La
solución? Mutexes.
https://riptutorial.com/es/home 15
exclusión mutua del recurso. Una vez que se termina de acceder al recurso, el hilo "libera" el
mutex. Mientras se adquiere el mutex, todas las llamadas para adquirir el mutex no volverán
hasta que se libere el mutex. Para entender mejor esto, piense en un mutex como una línea de
espera en el supermercado: los hilos se alinean al tratar de adquirir el mutex y luego esperar a
que los hilos por delante terminen, luego usar el recurso, luego salir del paso. línea liberando el
mutex. Habría pandemonium completo si todos trataran de acceder al recurso a la vez.
C ++ 11
#include <thread>
#include <mutex>
#include <iostream>
using namespace std;
int main() {
int var = 1;
mutex m;
https://riptutorial.com/es/home 16
Capítulo 3: Alcances
Examples
Alcance de bloque simple
{
int x = 100;
// ^
// Scope of `x` begins here
//
} // <- Scope of `x` ends here
Si un bloque anidado comienza dentro de un bloque externo, una nueva variable declarada con el
mismo nombre que está antes en la clase externa, oculta el primero.
{
int x = 100;
{
int x = 200;
Variables globales
Para declarar una instancia única de una variable a la que se puede acceder en diferentes
archivos de origen, es posible hacerlo en el ámbito global con la palabra clave extern . Esta
palabra clave le dice al compilador que en alguna parte del código hay una definición para esta
variable, por lo que puede usarse en todas partes y toda la escritura / lectura se realizará en un
lugar de la memoria.
// File my_globals.h:
#ifndef __MY_GLOBALS_H__
#define __MY_GLOBALS_H__
#endif
https://riptutorial.com/es/home 17
// File foo1.cpp:
#include "my_globals.h"
// File main.cpp:
#include "my_globals.h"
#include <iostream>
int main()
{
std::cout << "The radius is: " << circle_radius << "\n";'
return 0;
}
Salida:
https://riptutorial.com/es/home 18
Capítulo 4: Algoritmos de la biblioteca
estándar
Examples
std :: for_each
Efectos:
Parámetros:
Valor de retorno:
Complejidad:
Ejemplo:
c ++ 11
std::vector<int> v { 1, 2, 4, 8, 16 };
std::for_each(v.begin(), v.end(), [](int elem) { std::cout << elem << " "; });
Aplica la función dada para cada elemento del vector v imprimiendo este elemento a la stdout .
std :: next_permutation
Efectos:
https://riptutorial.com/es/home 19
Tamice la secuencia de datos del rango [primero, último) en la siguiente permutación
lexicográficamente más alta. Si se proporciona cmpFun , la regla de permutación se personaliza.
Parámetros:
first - el comienzo del rango a permutar, inclusive
last - el final del rango a ser permutado, exclusivo
Valor de retorno:
Devuelve true si existe tal permutación.
De lo contrario, el rango se cambia a la permutación lexicográficamente más pequeña y devuelve
false.
Complejidad:
O (n), n es la distancia del first al last .
Ejemplo :
123
132
213
231
312
321
std :: acumular
Efectos:
std :: Se acumula realiza la operación de plegado usando la función f en el rango [first, last)
comenzando con init como valor acumulador.
https://riptutorial.com/es/home 20
Efectivamente es equivalente a:
T acc = init;
for (auto it = first; first != last; ++it)
acc = f(acc, *it);
return acc;
En la versión (1) el operator+ se usa en lugar de f , por lo que acumular sobre el contenedor es
equivalente a la suma de los elementos del contenedor.
Parámetros:
Valor de retorno:
Complejidad:
Ejemplo:
std::vector<int> v { 2, 3, 4 };
auto sum = std::accumulate(v.begin(), v.end(), 1);
std::cout << sum << std::endl;
Salida:
10
c ++ 11
class Converter {
public:
int operator()(int a, int d) const { return a * 10 + d; }
};
y después
c ++ 11
https://riptutorial.com/es/home 21
const std::vector<int> ds = {1, 2, 3};
int n = std::accumulate(ds.begin(), ds.end(),
0,
[](int a, int d) { return a * 10 + d; });
std::cout << n << std::endl;
Salida:
123
std :: encontrar
Efectos
Parámetros
first => iterador que apunta al comienzo del rango last => iterador que apunta al final del rango
val => el valor a encontrar dentro del rango
Regreso
Un iterador que apunta al primer elemento dentro del rango que es igual (==) a val, el iterador
apunta a durar si no se encuentra val.
Ejemplo
#include <vector>
#include <algorithm>
#include <iostream>
//create a vector
vector<int> intVec {4, 6, 8, 9, 10, 30, 55,100, 45, 2, 4, 7, 9, 43, 48};
//define iterators
vector<int>::iterator itr_9;
vector<int>::iterator itr_43;
vector<int>::iterator itr_50;
//calling find
itr_9 = find(intVec.begin(), intVec.end(), 9); //occurs twice
itr_43 = find(intVec.begin(), intVec.end(), 43); //occurs once
https://riptutorial.com/es/home 22
cout << "first occurence of: " << *itr_9 << endl;
cout << "only occurence of: " << *itr_43 << Lendl;
/*
let's prove that itr_9 is pointing to the first occurence
of 9 by looking at the element after 9, which should be 10
not 43
*/
cout << "element after first 9: " << *(itr_9 + 1) << ends;
/*
to avoid dereferencing intVec.end(), lets look at the
element right before the end
*/
cout << "last element: " << *(itr_50 - 1) << endl;
return 0;
}
Salida
std :: cuenta
Efectos
Parámetros
Regreso
Ejemplo
#include <vector>
#include <algorithm>
#include <iostream>
https://riptutorial.com/es/home 23
int main(int argc, const char * argv[]) {
//create vector
vector<int> intVec{4,6,8,9,10,30,55,100,45,2,4,7,9,43,48};
//print result
cout << "There are " << count_9 << " 9s"<< endl;
cout << "There is " << count_55 << " 55"<< endl;
cout << "There is " << count_101 << " 101"<< ends;
//count its occurences in the vector starting from the first one
size_t count_4 = count(itr_4, intVec.end(), *itr_4); // should be 2
cout << "There are " << count_4 << " " << *itr_4 << endl;
return 0;
}
Salida
There are 2 9s
There is 1 55
There is 0 101
There are 2 4
std :: count_if
Efectos
Cuenta el número de elementos en un rango para el que una función de predicado especificada
es verdadera
Parámetros
first => iterador que apunta al principio del rango last => iterador que apunta al final del rango
red => función de predicado (devuelve verdadero o falso)
Regreso
El número de elementos dentro del rango especificado para el cual la función de predicado
devolvió verdadero.
https://riptutorial.com/es/home 24
Ejemplo
#include <iostream>
#include <vector>
#include <algorithm>
/*
Define a few functions to use as predicates
*/
//functor that returns true if number is greater than the value of the constructor parameter
provided
class Greater {
int _than;
public:
Greater(int th): _than(th){}
bool operator()(int i){
return i > _than;
}
};
//create a vector
vector<int> myvec = {1,5,8,0,7,6,4,5,2,1,5,0,6,9,7};
//using function pointer to count odd number in the first half of the vector
size_t oddCount = count_if(myvec.begin(), myvec.end()- myvec.size()/2, isOdd);
return 0;
}
Salida
vector size: 15
even numbers: 7 found
odd numbers: 4 found
numbers > 5: 6 found
https://riptutorial.com/es/home 25
std :: find_if
Efectos
Encuentra el primer elemento en un rango para el cual la función de predicado pred devuelve true.
Parámetros
first => iterador que apunta al principio del rango last => iterador que apunta al final del rango
pred => función de predicado (devuelve verdadero o falso)
Regreso
Un iterador que apunta al primer elemento dentro del rango para el que predice la función predate
devuelve true para. El iterador apunta a durar si no se encuentra val
Ejemplo
#include <iostream>
#include <vector>
#include <algorithm>
/*
define some functions to use as predicates
*/
public:
Greater(int th):_than(th){
}
bool operator()(int data) const
{
return data > _than;
}
};
int main()
{
https://riptutorial.com/es/home 26
//with a lambda function
vector<int>::iterator gt10 = find_if(myvec.begin(), myvec.end(), [](int x){return x>10;});
// >= C++11
//with functor
vector<int>::iterator gt5 = find_if(myvec.begin(), myvec.end(), Greater(5));
//not Found
vector<int>::iterator nf = find_if(myvec.begin(), myvec.end(), Greater(1000)); // nf points
to myvec.end()
cout << "First item > 10: " << *gt10 << endl;
cout << "First Item n * 10: " << *pow10 << endl;
cout << "First Item > 5: " << *gt5 << endl;
return 0;
}
Salida
std :: min_element
Efectos
Parámetros
https://riptutorial.com/es/home 27
toma dos argumentos y devuelve verdadero o falso, lo que indica si el argumento es menor que el
argumento 2. Esta función no debe modificar las entradas
Regreso
Complejidad
Ejemplo
#include <iostream>
#include <algorithm>
#include <vector>
#include <utility> //to use make_pair
//Using pairLessThanFunction
auto minPairFunction = min_element(pairVector.begin(), pairVector.end(),
pairLessThanFunction);
return 0;
}
Salida
https://riptutorial.com/es/home 28
Usando std :: nth_element para encontrar la mediana (u otros cuantiles)
Por el bien de este ejemplo, definamos la mediana de una secuencia de longitud n como el
elemento que estaría en la posición ⌈n / 2⌉. Por ejemplo, la mediana de una secuencia de longitud 5 es
el tercer elemento más pequeño, y también lo es la mediana de una secuencia de longitud 6.
Para usar esta función para encontrar la mediana, podemos usar lo siguiente. Digamos que
empezamos con
std::vector<int>::iterator b = v.begin();
std::vector<int>::iterator e = v.end();
std::vector<int>::iterator med = b;
std::advance(med, v.size() / 2);
std::advance(nth, pos);
https://riptutorial.com/es/home 29
Capítulo 5: Alineación
Introducción
Todos los tipos en C ++ tienen una alineación. Esta es una restricción en la dirección de memoria
dentro de la cual se pueden crear objetos de ese tipo. Una dirección de memoria es válida para la
creación de un objeto si la división de esa dirección por la alineación del objeto es un número
entero.
Las alineaciones de tipo son siempre una potencia de dos (incluido 1).
Observaciones
La norma garantiza lo siguiente:
• El requisito de alineación de un tipo es un divisor de su tamaño. Por ejemplo, una clase con
un tamaño de 16 bytes podría tener una alineación de 1, 2, 4, 8 o 16, pero no 32. (Si los
miembros de una clase solo tienen un tamaño de 14 bytes, pero la clase debe tener un
requisito de alineación de 8, el compilador insertará 2 bytes de relleno para hacer que el
tamaño de la clase sea igual a 16.)
• Las versiones firmadas y sin firmar de un tipo entero tienen el mismo requisito de alineación.
• Un puntero para void tiene el mismo requisito de alineación que un puntero para char .
• Las versiones calificadas para cv y no calificadas para cv de un tipo tienen el mismo
requisito de alineación.
Tenga en cuenta que si bien existe alineación en C ++ 03, no fue hasta C ++ 11 que se hizo
posible consultar la alineación (usando alignof ) y la alineación de control (utilizando alignas ).
Examples
Consultar la alineación de un tipo.
c ++ 11
El requisito de alineación de un tipo se puede consultar utilizando la palabra clave alignof como
un operador alignof . El resultado es una expresión constante de tipo std::size_t , es decir, se
puede evaluar en tiempo de compilación.
#include <iostream>
int main() {
std::cout << "The alignment requirement of int is: " << alignof(int) << '\n';
}
Salida posible
https://riptutorial.com/es/home 30
El requisito de alineación de int es: 4
Si se aplica a una matriz, produce el requisito de alineación del tipo de elemento. Si se aplica a un
tipo de referencia, produce el requisito de alineación del tipo referenciado. (Las referencias en sí
mismas no tienen alineación, ya que no son objetos).
Controlando la alineación
C ++ 11
La palabra clave alignas se puede usar para forzar a una variable, miembro de datos de clase,
declaración o definición de una clase, o declaración o definición de una enumeración, para tener
una alineación particular, si se admite. Se presenta en dos formas:
En este ejemplo, se garantiza que el búfer buf se alineará adecuadamente para contener un
objeto int , aunque su tipo de elemento sea un unsigned char , que puede tener un requisito de
alineación más débil.
alignas no se puede usar para dar a un tipo una alineación más pequeña que la que tendría el
tipo sin esta declaración:
alignas , cuando se le da una expresión constante entera, se le debe dar una alineación válida.
Las alineaciones válidas son siempre potencias de dos y deben ser mayores que cero. Los
compiladores deben admitir todas las alineaciones válidas hasta la alineación del tipo
std::max_align_t . Pueden apoyar las alineaciones más grandes que esto, pero el soporte para la
asignación de memoria para tales objetos es limitada. El límite superior de las alineaciones
depende de la implementación.
C ++ 17 cuenta con soporte directo en operator new para asignar memoria para tipos sobre
alineados.
https://riptutorial.com/es/home 31
Capítulo 6: Archivo I / O
Introducción
El archivo de C ++ I / O se realiza a través de secuencias . Las abstracciones clave son:
Las secuencias utilizan std::locale , por ejemplo, para detalles del formato y para la traducción
entre las codificaciones externas y la codificación interna.
Examples
Abriendo un archivo
std::fstream iofs("foo.txt"); // fstream: Opens file "foo.txt" for reading and writing.
std::ifstream ifs;
ifs.open("bar.txt"); // ifstream: Opens file "bar.txt" for reading only.
std::ofstream ofs;
ofs.open("bar.txt"); // ofstream: Opens file "bar.txt" for writing only.
std::fstream iofs;
iofs.open("bar.txt"); // fstream: Opens file "bar.txt" for reading and writing.
Usted debe comprobar siempre si un archivo ha sido abierto con éxito (incluso cuando se
escribe). Las fallas pueden incluir: el archivo no existe, el archivo no tiene los derechos de acceso
https://riptutorial.com/es/home 32
correctos, el archivo ya está en uso, se produjeron errores en el disco, la unidad se desconectó ...
La verificación se puede hacer de la siguiente manera:
Cuando la ruta del archivo contenga barras diagonales inversas (por ejemplo, en el sistema
Windows), debe eliminarlas correctamente:
C ++ 11
C ++ 11
Si desea abrir un archivo con caracteres no ASCII en la ruta en Windows actualmente, puede
usar el argumento de ruta de caracteres anchos no estándar :
Leyendo de un archivo
Si sabe cómo se formatean los datos, puede usar el operador de extracción de flujo ( >> ).
Supongamos que tiene un archivo llamado foo.txt que contiene los siguientes datos:
Luego puede usar el siguiente código para leer los datos del archivo:
https://riptutorial.com/es/home 33
// Define variables.
std::ifstream is("foo.txt");
std::string firstname, lastname;
int age, bmonth, bday, byear;
// Extract firstname, lastname, age, bday month, bday day, and bday year in that order.
// Note: '>>' returns false if it reached EOF (end of file) or if the input data doesn't
// correspond to the type of the input variable (for example, the string "foo" can't be
// extracted into an 'int' variable).
while (is >> firstname >> lastname >> age >> bmonth >> bday >> byear)
// Process the data that has been read.
El operador de extracción de flujo >> extrae cada carácter y se detiene si encuentra un carácter
que no se puede almacenar o si es un carácter especial:
• Para los tipos de cadena, el operador se detiene en un espacio en blanco ( ) o en una nueva
línea ( \n ).
• Para los números, el operador se detiene en un carácter no numérico.
Esto significa que la siguiente versión del archivo foo.txt también se leerá con éxito en el código
anterior:
John
Doe 25
4 6 1987
Jane
Doe
15 5
24
1976
El operador de extracción de flujo >> siempre devuelve el flujo dado a él. Por lo tanto, se pueden
encadenar varios operadores para leer datos consecutivamente. Sin embargo, una corriente
también se puede utilizar como una expresión booleana (como se muestra en el while bucle en el
código anterior). Esto se debe a que las clases de flujo tienen un operador de conversión para el
tipo bool . Este operador bool() devolverá true siempre que la secuencia no tenga errores. Si un
flujo entra en un estado de error (por ejemplo, porque no se pueden extraer más datos), el
operador bool() devolverá el valor false . Por lo tanto, el while de bucle en el código anterior se
saldrá después del archivo de entrada se ha leído hasta el final.
Si desea leer un archivo completo como una cadena, puede usar el siguiente código:
// Opens 'foo.txt'.
std::ifstream is("foo.txt");
std::string whole_file;
https://riptutorial.com/es/home 34
// Sets position to the start of the file.
is.seekg(0, std::ios::beg);
Este código reserva espacio para la string con el fin de reducir las asignaciones de memoria
innecesarias.
Si desea leer un archivo línea por línea, puede usar la función getline() :
std::ifstream is("foo.txt");
Si desea leer un número fijo de caracteres, puede usar la función miembro de la secuencia read()
:
std::ifstream is("foo.txt");
char str[4];
if (is.fail())
// Failed to read!
Escribiendo en un archivo
Hay varias formas de escribir en un archivo. La forma más sencilla es utilizar una secuencia de
archivo de salida ( ofstream ) junto con el operador de inserción de secuencia ( << ):
std::ofstream os("foo.txt");
if(os.is_open()){
os << "Hello World!";
}
En lugar de << , también puede usar la función miembro write() la secuencia del archivo de
salida:
std::ofstream os("foo.txt");
https://riptutorial.com/es/home 35
if(os.is_open()){
char data[] = "Foo";
os << "Hello Badbit!"; // This operation might fail for any reason.
if (os.bad())
// Failed to write!
Modos de apertura
Al crear una secuencia de archivos, puede especificar un modo de apertura. Un modo de apertura
es básicamente una configuración para controlar cómo la secuencia abre el archivo.
std::ifstream is;
is.open("foo.txt", std::ios::in | std::ios::binary);
Se debe tener en cuenta que debe configurar ios::in o ios::out si desea establecer otros
indicadores, ya que no están establecidos de forma implícita por los miembros de iostream
aunque tengan un valor predeterminado correcto.
• ifstream - in
• ofstream - out
• fstream - in y out
Los modos de apertura de archivos que puede especificar por diseño son:
https://riptutorial.com/es/home 36
Modo Sentido por Descripción
Nota: la configuración del modo binary permite que los datos se lean / escriban exactamente
como están; no configurarlo permite la traducción del carácter '\n' nueva línea '\n' a / desde una
secuencia de final de línea específica de la plataforma.
Cerrando un archivo
Cerrar un archivo explícitamente rara vez es necesario en C ++, ya que una secuencia de
archivos cerrará automáticamente su archivo asociado en su destructor. Sin embargo, debe
intentar limitar la vida útil de un objeto de flujo de archivos, de modo que no mantenga abierto el
manejador de archivos más de lo necesario. Por ejemplo, esto se puede hacer poniendo todas las
operaciones de archivo en un ámbito propio ( {} ):
// Write data.
output << prepared_data;
} // The ofstream will go out of scope here.
// Its destructor will take care of closing the file properly.
Llamar a close() explícitamente solo es necesario si desea reutilizar el mismo objeto fstream más
tarde, pero no desea mantener el archivo abierto entre:
// Preparing data might take a long time. Therefore, we don't open the output file stream
// before we actually can write some data to it.
std::string const more_prepared_data = prepare_complex_data();
// Open the file "foo.txt" for the second time once we are ready for writing.
output.open("foo.txt");
https://riptutorial.com/es/home 37
// Write the data to the file "foo.txt".
output << more_prepared_data;
Flushing un arroyo
std::ofstream os("foo.txt");
os << "Hello World!" << std::flush;
Hay un manipulador de flujo std::endl que combina la escritura de una nueva línea con la
descarga de flujo:
std::ifstream f("file.txt");
if (f)
{
std::stringstream buffer;
buffer << f.rdbuf();
f.close();
El método rdbuf() devuelve un puntero a un streambuf que puede streambuf en el buffer través de
la función miembro stringstream::operator<< .
https://riptutorial.com/es/home 38
std::ifstream f("file.txt");
if (f)
{
std::string str((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
// Operations on `str`...
}
Esto es bueno porque requiere poco código (y permite leer un archivo directamente en cualquier
contenedor STL, no solo cadenas) pero puede ser lento para archivos grandes.
NOTA : los paréntesis adicionales alrededor del primer argumento del constructor de cadenas son
esenciales para evitar el problema de análisis más desconcertante .
std::ifstream f("file.txt");
if (f)
{
f.seekg(0, std::ios::end);
const auto size = f.tellg();
// Operations on `str`...
}
En el siguiente ejemplo, usamos std::string y operator>> para leer los elementos del archivo.
std::ifstream file("file3.txt");
std::vector<std::string> v;
std::string s;
while(file >> s) // keep reading until we run out
{
v.push_back(s);
}
En el ejemplo anterior, simplemente estamos iterando a través del archivo leyendo un "elemento"
a la vez utilizando el operator>> . Este mismo efecto se puede lograr utilizando el
std::istream_iterator que es un iterador de entrada que lee un "elemento" a la vez desde el flujo.
Además, la mayoría de los contenedores se pueden construir utilizando dos iteradores, por lo que
https://riptutorial.com/es/home 39
podemos simplificar el código anterior para:
std::ifstream file("file3.txt");
std::vector<std::string> v(std::istream_iterator<std::string>{file},
std::istream_iterator<std::string>{});
Podemos extender esto para leer cualquier tipo de objeto que nos guste simplemente
especificando el objeto que queremos leer como el parámetro de plantilla para
std::istream_iterator . Por lo tanto, podemos simplemente extender lo anterior para leer líneas
(en lugar de palabras) como esta:
std::ifstream file("file3.txt");
C ++ 11
struct info_type
{
std::string name;
int age;
float height;
https://riptutorial.com/es/home 40
};
void func4()
{
auto file = std::ifstream("file4.txt");
std::vector<info_type> v;
for(info_type info; file >> info;) // keep reading until we run out
{
// we only get here if the read succeeded
v.push_back(info);
}
file4.txt
Wogger Wabbit
2
6.2
Bilbo Baggins
111
81.3
Mary Poppins
29
154.8
Salida:
Copiando un archivo
C ++ 17
https://riptutorial.com/es/home 41
Con C ++ 17, la forma estándar de copiar un archivo es incluir el encabezado <filesystem> y usar
copy_file :
std::fileystem::copy_file("source_filename", "dest_filename");
¿Revisar el final del archivo dentro de una condición de bucle, mala práctica?
eof devuelve true solo después de leer el final del archivo. NO indica que la próxima lectura será
el final de la transmisión.
while (!f.eof())
{
// Everything is OK
f >> buffer;
/* Use `buffer` */
}
while (!f.eof())
{
f >> buffer >> std::ws;
if (f.fail())
break;
/* Use `buffer` */
}
pero
Otras referencias:
https://riptutorial.com/es/home 42
Si necesita escribir un archivo con una configuración regional diferente a la predeterminada,
puede usar std::locale y std::basic_ios::imbue() para hacer eso para una secuencia de archivos
específica:
Guía de uso:
Razones para las restricciones: Imbuir una secuencia de archivos con una configuración
regional tiene un comportamiento indefinido si la configuración regional actual no es
independiente del estado o no apunta al principio del archivo.
Las transmisiones UTF-8 (y otras) no son independientes del estado. Además, una secuencia de
archivos con una configuración regional UTF-8 puede intentar leer el marcador de la lista de
materiales del archivo cuando se abre; así que solo abrir el archivo puede leer los caracteres del
archivo y no estará al principio.
#include <iostream>
#include <fstream>
#include <locale>
int main()
{
std::cout << "User-preferred locale setting is "
<< std::locale("").name().c_str() << std::endl;
El cambio explícito a la configuración regional clásica "C" es útil si su programa utiliza una
configuración regional predeterminada diferente y desea garantizar un estándar fijo para leer y
escribir archivos. Con una configuración regional preferida de "C", el ejemplo escribe
78,123.456
78,123.456
78123.456
https://riptutorial.com/es/home 43
Si, por ejemplo, el entorno local preferido es alemán y, por lo tanto, utiliza un formato de número
diferente, el ejemplo escribe
78 123,456
78,123.456
78123.456
https://riptutorial.com/es/home 44
Capítulo 7: Archivos de encabezado
Observaciones
En C ++, como en C, el compilador de C ++ y el proceso de compilación hacen uso del
preprocesador de C. Según lo especificado en el manual del preprocesador GNU C, un archivo de
encabezado se define de la siguiente manera:
• Los archivos de encabezado del sistema declaran las interfaces a partes del
sistema operativo. Los incluye en su programa para proporcionar las definiciones
y declaraciones que necesita para invocar llamadas de sistema y bibliotecas.
• Sus propios archivos de encabezado contienen declaraciones de interfaces entre
los archivos de origen de su programa. Cada vez que tenga un grupo de
declaraciones relacionadas y definiciones de macro, todas o la mayoría de las
cuales son necesarias en varios archivos de origen diferentes, es una buena
idea crear un archivo de encabezado para ellos.
Tenga en cuenta que los archivos de encabezado pueden ser reemplazados como una
convención de estructura de archivos de proyecto por la próxima característica de los módulos,
que aún debe considerarse para su inclusión en un futuro estándar de C ++ en el momento de la
escritura (por ejemplo, C ++ 20).
Examples
Ejemplo básico
El siguiente ejemplo contendrá un bloque de código que se debe dividir en varios archivos de
origen, como se indica en los comentarios de // filename .
https://riptutorial.com/es/home 45
Archivos fuente
// my_function.h
/* Also, usually header files include preprocessor guards so that every header
* is never included twice.
*
* The guard is implemented by checking if a header-file unique preprocessor
* token is defined, and only including the header if it hasn't been included
* once before.
*/
#ifndef MY_FUNCTION_H
#define MY_FUNCTION_H
#endif // MY_FUNCTION_H
// my_function.cpp
/* Note how the corresponding source file for the header includes the interface
* defined in the header so that the compiler is aware of what the source file is
* implementing.
*
* In this case, the source file requires knowledge of the global constant
* global_value only defined in my_function.h. Without inclusion of the header
* file, this source file would not compile.
*/
#include "my_function.h" // or #include "my_function.hpp"
int my_function() {
return global_value; // return 42;
}
Los archivos de encabezado luego se incluyen en otros archivos de origen que desean utilizar la
funcionalidad definida por la interfaz del encabezado, pero no requieren conocimiento de su
implementación (por lo tanto, reduciendo el acoplamiento de código). El siguiente programa hace
uso del encabezado my_function.h como se definió anteriormente:
// main.cpp
https://riptutorial.com/es/home 46
}
El proceso de compilación
Dado que los archivos de encabezado a menudo forman parte de un flujo de trabajo del proceso
de compilación, un proceso de compilación típico que hace uso de la convención de archivo de
encabezado / fuente generalmente hará lo siguiente.
Alternativamente, si uno desea compilar main.cpp en un archivo de objeto primero, y luego vincular
solo los archivos de objeto como el paso final:
g++ -c my_function.cpp
g++ -c main.cpp
Esto significa que la función de plantilla, la función de miembro y las definiciones de clase no
pueden delegarse a un archivo de código fuente separado, ya que cualquier código que utilizará
cualquier construcción con plantilla requiere conocimiento de su definición para generar
generalmente cualquier código derivado.
// templated_function.h
https://riptutorial.com/es/home 47
Lea Archivos de encabezado en línea: https://riptutorial.com/es/cplusplus/topic/7211/archivos-de-
encabezado
https://riptutorial.com/es/home 48
Capítulo 8: Aritmética de punto flotante
Examples
Los números de punto flotante son raros
El primer error que casi todos los programadores cometen es suponer que este código funcionará
según lo previsto:
float total = 0;
for(float a = 0; a != 2; a += 0.01f) {
total += a;
}
El programador novato asume que esto resumirá cada número en el rango 0, 0.01, 0.02, 0.03,
..., 1.97, 1.98, 1.99 , para obtener el resultado 199 la respuesta matemáticamente correcta.
1. El programa como está escrito nunca concluye. a nunca se vuelve igual a 2 , y el bucle
nunca termina.
2. Si reescribimos la lógica del bucle para verificar a < 2 lugar, el bucle termina, pero el total
termina siendo algo diferente de 199 . En las máquinas compatibles con IEEE754, a menudo
suman aproximadamente 201 lugar.
La razón por la que esto sucede es que los números de punto flotante representan
aproximaciones de sus valores asignados .
double a = 0.1;
double b = 0.2;
double c = 0.3;
if(a + b == c)
//This never prints on IEEE754-compliant machines
std::cout << "This Computer is Magic!" << std::endl;
else
std::cout << "This Computer is pretty normal, all things considered." << std::endl;
Si bien lo que vemos el programador son tres números escritos en base10, lo que ven el
compilador (y el hardware subyacente) son números binarios. Debido a que 0.1 , 0.2 y 0.3
requieren una división perfecta por 10 cual es bastante fácil en un sistema base-10, pero
imposible en un sistema base-2, estos números deben almacenarse en formatos imprecisos, de
manera similar a como se muestra el número 1/3 tiene que ser almacenado en la forma imprecisa
0.333333333333333... en base-10.
https://riptutorial.com/es/home 49
representation of 0.1
double b = 0011111111001001100110011001100110011001100110011001100110011010; //imperfect
representation of 0.2
double c = 0011111111010011001100110011001100110011001100110011001100110011; //imperfect
representation of 0.3
double a + b = 0011111111010011001100110011001100110011001100110011001100110100; //Note that
this is not quite equal to the "canonical" 0.3!
https://riptutorial.com/es/home 50
Capítulo 9: Arrays
Introducción
Las matrices son elementos del mismo tipo que se colocan en ubicaciones de memoria contiguas.
Los elementos pueden ser referenciados individualmente por un identificador único con un índice
agregado.
Esto le permite declarar múltiples valores de variables de un tipo específico y acceder a ellos
individualmente sin necesidad de declarar una variable para cada valor.
Examples
Tamaño de matriz: tipo seguro en tiempo de compilación.
//----------------------------------- Machinery:
//----------------------------------- Usage:
#include <iostream>
using namespace std;
auto main()
-> int
{
int const a[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
Size const n = n_items( a );
int b[n] = {}; // An array of the same size as a.
(void) b;
cout << "Size = " << n << "\n";
}
Para C ++ 11
std::extent<decltype(MyArray)>::value;
https://riptutorial.com/es/home 51
Ejemplo:
Con C ++ 17 y versiones posteriores, se puede usar std::size , que está especializado para
arreglos.
// Example of raw dynamic size array. It's generally better to use std::vector.
#include <algorithm> // std::sort
#include <iostream>
using namespace std;
auto main()
-> int
{
cout << "Sorting n integers provided by you.\n";
cout << "n? ";
int const n = int_from( cin );
int* a = new int[n]; // ← Allocation of array of n items.
sort( a, a + n );
for( int i = 0; i < n; ++i ) { cout << a[i] << ' '; }
cout << '\n';
delete[] a;
}
Un programa que declara una matriz T a[n]; donde n se determina un tiempo de ejecución, se
puede compilar con ciertos compiladores que admiten matrices de longitud variable ( VLA) C99
como una extensión de lenguaje. Pero los VLA no son compatibles con C ++ estándar. Este
ejemplo muestra cómo asignar manualmente una matriz de tamaño dinámico a través de una
new[] expresión new[] ,
https://riptutorial.com/es/home 52
... luego utilícelo, y finalmente deséchelo mediante una delete[] -expresión:
delete[] a;
La matriz asignada aquí tiene valores indeterminados, pero se puede inicializar con cero
simplemente agregando un paréntesis vacío () , así: new int[n]() . Más generalmente, para un
tipo de elemento arbitrario, esto realiza una inicialización de valores .
Como parte de una función en una jerarquía de llamadas, este código no sería seguro de
excepción, ya que una excepción antes de la expresión delete[] (y después de la new[] ) causaría
una pérdida de memoria. Una forma de abordar ese problema es automatizar la limpieza, por
ejemplo, mediante un puntero inteligente std::unique_ptr . Pero una forma generalmente mejor de
abordarlo es simplemente usar std::vector : para eso está std::vector .
int main()
{
cout << "Sorting integers provided by you.\n";
cout << "You can indicate EOF via F6 in Windows or Ctrl+D in Unix-land.\n";
vector<int> a; // ← Zero size by default.
while( cin )
{
cout << "One number, please, or indicate EOF: ";
int const x = int_from( cin );
if( !cin.fail() ) { a.push_back( x ); } // Expands as necessary.
}
std::vector es una plantilla de clase de biblioteca estándar que proporciona la noción de una
matriz de tamaño variable. Se encarga de toda la administración de la memoria, y el búfer es
contiguo, por lo que se puede pasar un puntero al búfer (por ejemplo, &v[0] o v.data() ) a las
funciones de API que requieren una matriz sin formato. Incluso se puede expandir un vector en
tiempo de ejecución, por ejemplo, a través de la función miembro push_back que agrega un
elemento.
https://riptutorial.com/es/home 53
Internamente, esto generalmente se logra cuando el vector duplica su tamaño de búfer, su
capacidad, cuando se necesita un búfer más grande. Por ejemplo, para un búfer que comienza
como tamaño 1 y se duplica repetidamente según sea necesario para n = 17 llamadas push_back ,
esto implica 1 + 2 + 4 + 8 + 16 = 31 operaciones de copia, que es menos de 2 × n = 34. Y más
generalmente, la suma de esta secuencia no puede exceder de 2 × n .
En comparación con el ejemplo de matriz sin formato de tamaño dinámico, este código basado en
vector no requiere que el usuario suministre (y sepa) el número de elementos por adelantado. En
su lugar, el vector se expande según sea necesario, para cada nuevo valor de elemento
especificado por el usuario.
Una matriz de matriz sin formato de tamaño fijo (es decir, una matriz sin
formato 2D).
Salida:
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
C ++ no admite sintaxis especial para indexar una matriz multidimensional. En su lugar, una
matriz de este tipo se ve como una matriz de matrices (posiblemente de matrices, etc.), y se
utiliza la notación de índice único ordinario [ i ] para cada nivel. En el ejemplo anterior, m[y]
refiere a la fila y de m , donde y es un índice basado en cero. Entonces esta fila puede ser
indexado a su vez, por ejemplo, m[y][x] , que se refiere a la x ésimo elemento - o columna - de fila
y.
https://riptutorial.com/es/home 54
Es decir, el último índice varía más rápidamente, y en la declaración, el rango de este índice, que
es el número de columnas por fila, es el último y el tamaño "más interno" especificado.
Si no desea una dependencia en Boost o alguna otra biblioteca, entonces la matriz de tamaño
dinámico de un hombre pobre en C ++ es como
vector<vector<int>> m( 3, vector<int>( 7 ) );
… Donde vector es std::vector . La matriz se crea aquí copiando un vector de fila n veces donde
n es el número de filas, aquí 3. Tiene la ventaja de proporcionar la misma notación de indexación
m[y][x] que para una matriz de matriz sin formato de tamaño fijo, pero es un poco ineficiente
porque implica una asignación dinámica para cada fila, y es un poco inseguro porque es posible
cambiar inadvertidamente el tamaño de una fila.
Un enfoque más seguro y eficiente es usar un solo vector como almacenamiento para la matriz y
asignar el código del cliente ( x , y ) a un índice correspondiente en ese vector:
//--------------------------------------------- Machinery:
#include <algorithm> // std::copy
#include <assert.h> // assert
#include <initializer_list> // std::initializer_list
#include <vector> // std::vector
#include <stddef.h> // ptrdiff_t
namespace my {
using Size = ptrdiff_t;
using std::initializer_list;
using std::vector;
https://riptutorial.com/es/home 55
vector<Item> items_;
Size n_cols_;
public:
auto n_rows() const -> Size { return items_.size()/n_cols_; }
auto n_cols() const -> Size { return n_cols_; }
Matrix(): n_cols_( 0 ) {}
//--------------------------------------------- Usage:
using my::Matrix;
auto some_matrix()
-> Matrix<int>
{
return
{
{ 1, 2, 3, 4, 5, 6, 7 },
{ 8, 9, 10, 11, 12, 13, 14 },
{ 15, 16, 17, 18, 19, 20, 21 }
};
}
#include <iostream>
#include <iomanip>
using namespace std;
auto main() -> int
{
Matrix<int> const m = some_matrix();
assert( m.n_cols() == 7 );
assert( m.n_rows() == 3 );
https://riptutorial.com/es/home 56
for( int y = 0, y_end = m.n_rows(); y < y_end; ++y )
{
for( int x = 0, x_end = m.n_cols(); x < x_end; ++x )
{
cout << setw( 4 ) << m.item( x, y ); // ← Note: not `m[y][x]`!
}
cout << '\n';
}
}
Salida:
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
El código anterior no es de grado industrial: está diseñado para mostrar los principios básicos y
satisfacer las necesidades de los estudiantes que aprenden C ++.
Por ejemplo, uno puede definir sobrecargas de operator() para simplificar la notación de
indexación.
Inicialización de matriz
Una matriz es solo un bloque de ubicaciones de memoria secuencial para un tipo específico de
variable. Las matrices se asignan de la misma manera que las variables normales, pero con
corchetes anexados a su nombre [] que contienen el número de elementos que caben en la
memoria de la matriz.
El siguiente ejemplo de una matriz utiliza el tipo int , el nombre variable arrayOfInts y el número
de elementos [5] que la matriz tiene espacio:
int arrayOfInts[5];
Al inicializar una matriz al enumerar todos sus miembros, no es necesario incluir el número de
elementos dentro de los corchetes. Será calculado automáticamente por el compilador. En el
siguiente ejemplo, es 5:
También es posible inicializar solo los primeros elementos mientras se asigna más espacio. En
este caso, es obligatorio definir la longitud entre paréntesis. Lo siguiente asignará una matriz de
longitud 5 con inicialización parcial, el compilador inicializa todos los elementos restantes con el
valor estándar del tipo de elemento, en este caso cero.
https://riptutorial.com/es/home 57
int arrayOfInts[5] = {10,20}; // means 10, 20, 0, 0, 0
Las matrices de otros tipos de datos básicos pueden inicializarse de la misma manera.
char arrayOfChars[5]; // declare the array and allocate the memory, don't initialize
char arrayOfChars[5] = { 'a', 'b', 'c', 'd', 'e' } ; //declare and initialize
También es importante tener en cuenta que al acceder a los elementos de la matriz, el índice de
elementos de la matriz (o posición) comienza desde 0.
int array[5] = { 10/*Element no.0*/, 20/*Element no.1*/, 30, 40, 50/*Element no.4*/};
std::cout << array[4]; //outputs 50
std::cout << array[0]; //outputs 10
https://riptutorial.com/es/home 58
Capítulo 10: Atributos
Sintaxis
• [[detalles]]: atributo simple sin argumentos
Examples
[[sin retorno]]
C ++ 11
C ++ 11 introdujo el atributo [[noreturn]] . Se puede usar para que una función indique que la
función no regresa a la persona que llama, ya sea ejecutando una declaración de retorno , o
llegando al final si es cuerpo (es importante tener en cuenta que esto no se aplica a las funciones
void , ya que devuelva a la persona que llama, simplemente no devuelven ningún valor). Dicha
función puede terminar llamando a std::terminate o std::exit , o lanzando una excepción.
También vale la pena señalar que una función de este tipo puede regresar ejecutando longjmp .
Por ejemplo, la función a continuación siempre lanzará una excepción o llamará std::terminate ,
por lo que es un buen candidato para [[noreturn]] :
Este tipo de funcionalidad permite al compilador finalizar una función sin una declaración de
retorno si sabe que el código nunca se ejecutará. Aquí, debido a que la llamada a
ownAssertFailureHandler (definida anteriormente) en el código a continuación nunca regresará, el
compilador no necesita agregar código debajo de esa llamada:
https://riptutorial.com/es/home 59
ownAssertFailureHandler("Negative number passed to createSequence()"s);
// return std::vector<int>{}; //< Not needed because of [[noreturn]]
}
Tenga en cuenta que [[noreturn]] se utiliza principalmente en las funciones void. Sin embargo,
esto no es un requisito, permitiendo que las funciones se usen en la programación genérica:
template<class InconsistencyHandler>
double fortyTwoDivideBy(int i) {
if (i == 0)
i = InconsistencyHandler::correct(i);
return 42. / i;
}
struct InconsistencyThrower {
static [[noreturn]] int correct(int i) { ownAssertFailureHandler("Unknown
inconsistency"s); }
}
struct InconsistencyChangeToOne {
static int correct(int i) { return 1; }
}
• std :: abortar
• std :: exit
• std :: quick_exit
• std :: inesperado
• std :: terminar
• std :: rethrow_exception
• std :: throw_with_nested
• std :: nested_exception :: rethrow_nested
[[caer a través]]
C ++ 17
Cada vez que se termina un case en un switch , se ejecutará el código del siguiente caso. Este
último se puede prevenir usando la declaración 'break'. Dado que este llamado comportamiento
https://riptutorial.com/es/home 60
fallido puede introducir errores cuando no está previsto, varios compiladores y analizadores
estáticos emiten una advertencia al respecto.
switch(input) {
case 2011:
case 2014:
case 2017:
std::cout << "Using modern C++" << std::endl;
[[fallthrough]]; // > No warning
case 1998:
case 2003:
standard = input;
}
Consulte la propuesta para obtener ejemplos más detallados sobre cómo se puede usar
[[fallthrough]] .
C ++ 14
// Provides specific message which helps other programmers fixing there code
[[deprecated("Use the variant with unique_ptr instead, this function will be removed in the
next release")]]
void function(std::auto_ptr<A> a);
https://riptutorial.com/es/home 61
[[nodiscard]]
C ++ 17
El atributo [[nodiscard]] se puede usar para indicar que el valor de retorno de una función no
debe ignorarse cuando se realiza una llamada de función. Si se ignora el valor de retorno, el
compilador debe dar una advertencia sobre esto. El atributo se puede agregar a:
Agregar el atributo a un tipo tiene el mismo comportamiento que agregar el atributo a cada
función que devuelve este tipo.
template<typename Function>
[[nodiscard]] Finally<std::decay_t<Function>> onExit(Function &&f);
Consulte la propuesta para obtener ejemplos más detallados sobre cómo se puede usar
[[nodiscard]] .
Nota: Los detalles de la implementación de Finally / onExit se omiten en el ejemplo, vea Finally /
ScopeExit .
[[maybe_unused]]
El atributo [[maybe_unused]] se crea para indicar en el código que cierta lógica podría no ser
utilizada. Esto se vincula a menudo con las condiciones del preprocesador, donde se puede usar
o no. Como los compiladores pueden dar advertencias sobre variables no utilizadas, esta es una
forma de suprimirlas indicando la intención.
Un ejemplo típico de las variables que se necesitan en las construcciones de depuración cuando
no se necesitan en producción son los valores de retorno que indican el éxito. En las
compilaciones de depuración, la condición debe ser confirmada, aunque en producción, estas
afirmaciones se han eliminado.
Un ejemplo más complejo son diferentes tipos de funciones de ayuda que se encuentran en un
https://riptutorial.com/es/home 62
espacio de nombres sin nombre. Si estas funciones no se utilizan durante la compilación, un
compilador puede dar una advertencia sobre ellas. Idealmente, le gustaría protegerlos con las
mismas etiquetas de preprocesador que la persona que llama, aunque como esto podría volverse
complejo, el atributo [[maybe_unused]] es una alternativa más [[maybe_unused]] mantener.
namespace {
[[maybe_unused]] std::string createWindowsConfigFilePath(const std::string &relativePath);
// TODO: Reuse this on BSD, MAC ...
[[maybe_unused]] std::string createLinuxConfigFilePath(const std::string &relativePath);
}
Consulte la propuesta para obtener ejemplos más detallados sobre cómo se puede usar
[[maybe_unused]] .
https://riptutorial.com/es/home 63
Capítulo 11: auto
Observaciones
La palabra clave auto es un nombre de tipo que representa un tipo deducido automáticamente.
Ya era una palabra clave reservada en C ++ 98, heredada de C. En versiones anteriores de C ++,
se podía usar para indicar explícitamente que una variable tiene una duración de almacenamiento
automática:
int main()
{
auto int i = 5; // removing auto has no effect
}
Examples
Muestra auto básica
La palabra clave auto proporciona la deducción automática del tipo de una variable.
con lambdas :
https://riptutorial.com/es/home 64
auto myMap = std::map<int,float>();
myMap.emplace(1,3.14);
auto también puede causar problemas cuando las plantillas de expresión entran en juego:
auto mult(int c) {
return c * std::valarray<int>{1};
}
auto v = mult(3);
std::cout << v[0]; // some value that could be, but almost certainly is not, 3.
La razón es que el operator* en valarray le proporciona un objeto proxy que se refiere al valarray
como un medio de evaluación perezosa. Al usar auto , estás creando una referencia que cuelga.
En lugar de mult ha devuelto un std::valarray<int> , entonces el código definitivamente se
imprimiría 3.
La palabra clave auto por sí misma representa un tipo de valor, similar a int o char . Se puede
modificar con la palabra clave const y el símbolo & para representar un tipo const o un tipo de
referencia, respectivamente. Estos modificadores se pueden combinar.
En este ejemplo, s es un tipo de valor (su tipo se deducirá como std::string ), por lo que cada
iteración del bucle for copia una cadena del vector en s .
Si el cuerpo del bucle modifica s (por ejemplo, al llamar a s.append(" and stuff") ), solo se
modificará esta copia, no el miembro original de las strings .
Por otro lado, si s se declara con auto& será un tipo de referencia (se inferirá que es std::string& ),
por lo que en cada iteración del bucle se le asignará una referencia a una cadena en el vector:
for(auto& s : strings) {
std::cout << s << std::endl;
}
https://riptutorial.com/es/home 65
Finalmente, si s se declara const auto& , será un tipo de referencia const, lo que significa que en
cada iteración del bucle se le asignará una referencia const a una cadena en el vector:
Dentro del cuerpo de este bucle, s no se puede modificar (es decir, no hay métodos no const
pueden ser llamados en él).
Cuando se utiliza el modo auto con el rango for bucles, generalmente es una buena práctica usar
const auto& si el cuerpo del bucle no modifica la estructura que se está repitiendo, ya que esto
evita copias innecesarias.
que es equivalente a
int main() {}
Mayormente útil combinado con decltype para usar parámetros en lugar de std::declval<T> :
C ++ 14
auto print = [](const auto& arg) { std::cout << arg << std::endl; };
print(42);
print("hello world");
struct lambda {
template <typename T>
auto operator ()(const T& arg) const {
std::cout << arg << std::endl;
}
};
https://riptutorial.com/es/home 66
y entonces
lambda print;
print(42);
print("hello world");
A veces, el auto puede comportarse de la forma no esperada por un programador. El tipo deduce
la expresión, incluso cuando la deducción de tipo no es lo correcto.
Aquí la flag no sería bool , pero std::vector<bool>::reference , ya que para la especialización bool
del vector de plantilla el operator [] devuelve un objeto proxy con el operador de conversión
operator bool definido.
std::vector<bool> getFlags();
En casos como este, puede declarar una variable con auto e inicializarla mediante la conversión al
tipo que desea deducir:
pero en ese punto, simplemente reemplazar el auto por bool tiene más sentido.
Otro caso donde los objetos proxy pueden causar problemas son las plantillas de expresión . En
ese caso, las plantillas a veces no están diseñadas para durar más allá de la expresión completa
actual por razones de eficiencia, y el uso del objeto proxy en la siguiente causa un
comportamiento indefinido.
https://riptutorial.com/es/home 67
Capítulo 12: Bucles
Introducción
Una instrucción de bucle ejecuta un grupo de instrucciones repetidamente hasta que se cumple
una condición. Hay 3 tipos de bucles primitivos en C ++: para, while y do ... while.
Sintaxis
• sentencia while ( condición );
• hacer enunciado while ( expresión );
• para ( para-init-sentencia ; condición ; expresión ) sentencia ;
• para (para-gama-declaración: Con fines de gama-inicializador) Declaración;
• rotura ;
• continuar
Observaciones
algorithm llamadas de algorithm son generalmente preferibles a los bucles escritos a mano.
Si desea algo que ya hace un algoritmo (o algo muy similar), la llamada al algoritmo es más clara,
a menudo más eficiente y menos propensa a errores.
Si necesita un bucle que haga algo bastante simple (pero requeriría una confusa maraña de
carpetas y adaptadores si estuviera usando un algoritmo), simplemente escriba el bucle.
Examples
Basado en rango para
C ++ 11
for bucles pueden utilizarse para recorrer en iteración los elementos de un rango basado en
iteradores, sin usar un índice numérico o acceder directamente a los iteradores:
for(auto val: v)
{
std::cout << val << " ";
}
Esto iterará sobre cada elemento en v , con val obteniendo el valor del elemento actual. La
siguiente declaración:
https://riptutorial.com/es/home 68
for (for-range-declaration : for-range-initializer ) statement
es equivalente a:
{
auto&& __range = for-range-initializer;
auto __begin = begin-expr, __end = end-expr;
for (; __begin != __end; ++__begin) {
for-range-declaration = *__begin;
statement
}
}
C ++ 17
{
auto&& __range = for-range-initializer;
auto __begin = begin-expr;
auto __end = end-expr; // end is allowed to be a different type than begin in C++17
for (; __begin != __end; ++__begin) {
for-range-declaration = *__begin;
statement
}
}
{
auto&& __range = v;
auto __begin = v.begin(), __end = v.end();
for (; __begin != __end; ++__begin) {
auto val = *__begin;
std::cout << val << " ";
}
}
Tenga en cuenta que auto val declara un tipo de valor, que será una copia de un valor
almacenado en el rango (lo estamos inicializando desde el iterador). Si los valores almacenados
en el rango son costosos de copiar, es posible que desee utilizar const auto &val . Tampoco está
obligado a utilizar auto ; puede usar un nombre de tipo apropiado, siempre que sea implícitamente
convertible del tipo de valor del rango.
Si necesita acceso al iterador, basado en rango no puede ayudarlo (no sin esfuerzo, al menos).
for(float &val: v)
{
std::cout << val << " ";
}
https://riptutorial.com/es/home 69
Podría iterar en la referencia const si tiene un contenedor const :
vector<bool> v(10);
for(auto&& val: v)
{
val = true;
}
El tipo de "gama" proporcionada al intervalo basado- for puede ser uno de los siguientes:
• Matrices de idiomas:
• Cualquier tipo que tenga funciones miembro begin() y end() , que devuelve los iteradores a
los elementos del tipo. Los contenedores de la biblioteca estándar califican, pero los tipos
definidos por el usuario también se pueden usar:
struct Rng
{
float arr[3];
https://riptutorial.com/es/home 70
};
int main()
{
Rng rng = {{0.4f, 12.5f, 16.234f}};
• Cualquier tipo que tenga funciones de begin(type) y end(type) que no sean miembros que se
pueden encontrar mediante búsqueda dependiente de argumento, según el type . Esto es
útil para crear un tipo de rango sin tener que modificar el tipo de clase en sí:
namespace Mine
{
struct Rng {float arr[3];};
int main()
{
Mine::Rng rng = {{0.4f, 12.5f, 16.234f}};
En bucle
Un bucle for ejecuta instrucciones en el loop body del loop body , mientras que la condition del
bucle es verdadera. Antes de que la initialization statement bucle se ejecute exactamente una
vez. Después de cada ciclo, se ejecuta la parte de iteration execution .
• initialization statement: esta sentencia se ejecuta solo una vez, al comienzo del bucle for
. Puede ingresar una declaración de múltiples variables de un tipo, como int i = 0, a = 2, b
= 3
https://riptutorial.com/es/home 71
. Estas variables solo son válidas en el ámbito del bucle. Las variables definidas antes del
bucle con el mismo nombre se ocultan durante la ejecución del bucle.
• condition : esta declaración se evalúa antes de cada ejecución del cuerpo del bucle y
cancela el bucle si se evalúa como false .
• iteration execution : esta instrucción se ejecuta después del cuerpo del bucle, antes de la
siguiente evaluación de la condición , a menos que el bucle for sea abortado en el cuerpo
(por break , goto , return o una excepción lanzada). Puede ingresar varias declaraciones en
la parte de iteration execution , como a++, b+=10, c=b+a .
/*initialization*/
while (/*condition*/)
{
// body of the loop; using 'continue' will skip to increment part below
/*iteration execution*/
}
El caso más común para usar un bucle for es ejecutar sentencias un número específico de veces.
Por ejemplo, considere lo siguiente:
Notas:
https://riptutorial.com/es/home 72
razones de legibilidad, es una buena práctica realizar solo operaciones directamente
relevantes para el bucle.
• Una variable declarada en la declaración de inicialización es visible solo dentro del alcance
del bucle for y se libera al finalizar el bucle.
• No olvide que la variable que se declaró en la initialization statement se puede modificar
durante el bucle, así como la variable verificada en la condition .
• int counter = 0 inicializa la variable counter a 0. (Esta variable solo se puede usar dentro del
bucle for ).
• counter <= 10es una condición booleana que verifica si el counter es menor o igual a 10. Si
es true , el bucle se ejecuta. Si es false , el bucle termina.
• ++counter es una operación de incremento que incrementa el valor de counter en 1 antes de
la siguiente verificación de condición.
// infinite loop
for (;;)
std::cout << "Never ending!\n";
// infinite loop
while (true)
std::cout << "Never ending!\n";
Sin embargo, aún se puede dejar un bucle infinito utilizando las instrucciones break , goto o return
o lanzando una excepción.
El siguiente ejemplo común de iteración sobre todos los elementos de una colección STL (por
ejemplo, un vector ) sin usar el encabezado <algorithm> es:
Mientras bucle
Un while de bucle ejecuta las instrucciones varias veces hasta que la condición dada se evalúa
https://riptutorial.com/es/home 73
como false . Esta declaración de control se usa cuando no se sabe, de antemano, cuántas veces
se debe ejecutar un bloque de código.
Por ejemplo, para imprimir todos los números del 0 al 9, se puede usar el siguiente código:
int i = 0;
while (i < 10)
{
std::cout << i << " ";
++i; // Increment counter
}
std::cout << std::endl; // End of line; "0 1 2 3 4 5 6 7 8 9" is printed to the console
C ++ 17
Tenga en cuenta que desde C ++ 17, las 2 primeras declaraciones pueden combinarse
while (true)
{
// Do something forever (however, you can exit the loop by calling 'break'
}
Hay otra variante de while bucles, a saber, do...while construct. Vea el ejemplo del bucle do-while
para más información.
while(std::shared_ptr<Object> p = get_object()) {
p->do_something();
}
// p is no longer in scope.
Sin embargo, no está permitido hacer lo mismo con un bucle do...while while; en su lugar, declare
la variable antes del bucle, y (opcionalmente) encierre tanto la variable como el bucle dentro de
un ámbito local si desea que la variable salga del alcance después de que finalice el bucle:
https://riptutorial.com/es/home 74
//This doesn't compile
do {
s = do_something();
} while (short s > 0);
// Good
short s;
do {
s = do_something();
} while (s > 0);
Esto se debe a que la parte de instrucción de un bucle do...while while (el cuerpo del bucle) se
evalúa antes de llegar a la parte de expresión ( while ), y por lo tanto, cualquier declaración en la
expresión no será visible durante la primera iteración de lazo.
Bucle Do-while
Un bucle do-while es muy similar a un bucle while, excepto que la condición se comprueba al final
de cada ciclo, no al principio. Por lo tanto, se garantiza que el bucle se ejecuta al menos una vez.
El siguiente código imprimirá 0 , ya que la condición se evaluará como false al final de la primera
iteración:
int i =0;
do
{
std::cout << i;
++i; // Increment counter
}
while (i < 0);
std::cout << std::endl; // End of line; 0 is printed to the console
En contraste con el bucle do- while, lo siguiente no imprimirá nada, porque la condición se evalúa
como false al comienzo de la primera iteración:
int i =0;
while (i < 0)
{
std::cout << i;
++i; // Increment counter
}
std::cout << std::endl; // End of line; nothing is printed to the console
Nota: Un bucle while se puede salir sin la condición de convertirse en falsa utilizando una break ,
goto , o return comunicado.
int i = 0;
do
{
std::cout << i;
https://riptutorial.com/es/home 75
++i; // Increment counter
if (i > 5)
{
break;
}
}
while (true);
std::cout << std::endl; // End of line; 0 1 2 3 4 5 is printed to the console
Un bucle do-while trivial también es ocasionalmente usado para escribir macros que requieren su
propio ámbito (en cuyo caso el punto y coma final se omite de la definición de la macro y requiere
ser proporcionado por el usuario):
Las instrucciones de control de bucle se utilizan para cambiar el flujo de ejecución desde su
secuencia normal. Cuando la ejecución deja un ámbito, se destruyen todos los objetos
automáticos que se crearon en ese ámbito. El break y continue son instrucciones de control de
bucle.
1
2
3
La declaración de continue no sale inmediatamente del bucle, sino que se salta el resto del cuerpo
del bucle y va a la parte superior del bucle (incluida la verificación de la condición).
https://riptutorial.com/es/home 76
does not execute */
std::cout << i << " is an odd number\n";
}
1 is an odd number
3 is an odd number
5 is an odd number
Debido a que tales cambios en el flujo de control a veces son difíciles de entender para los
humanos, los break y los continue se usan con moderación. Una implementación más sencilla
suele ser más fácil de leer y entender. Por ejemplo, el primer bucle for con la break anterior podría
reescribirse como:
Usando bucles de rango de base, puede recorrer una subparte de un contenedor u otro rango
dado generando un objeto proxy que califica para bucles basados en rango.
https://riptutorial.com/es/home 77
}
};
template<class C>
auto except_first( C& c ) {
auto r = range(c);
if (r.empty()) return r;
return r.without_front();
}
std::vector<int> v = {1,2,3,4};
e imprimir
2
3
4
Tenga en cuenta que los objetos intermedios generados en la parte for(:range_expression) del
bucle for habrán caducado antes for comience el bucle for .
https://riptutorial.com/es/home 78
Capítulo 13: Búsqueda de nombre
dependiente del argumento
Examples
Que funciones se encuentran
Todas las funciones y plantillas dentro de todos los espacios de nombres asociados se
encuentran por búsqueda dependiente del argumento. Además, se encuentran funciones de
amigo en el ámbito del espacio de nombres declaradas en clases asociadas , que normalmente
no son visibles. Sin embargo, se ignoran las directivas de uso.
Todas las siguientes llamadas de ejemplo son válidas, sin calificar f por el nombre del espacio de
nombres en la llamada.
namespace A {
https://riptutorial.com/es/home 79
struct Z { };
namespace I { void g(Z); }
using namespace I;
// example calls
f(A::X());
f(A::X::Y());
f(std::make_shared<A::X>());
https://riptutorial.com/es/home 80
Capítulo 14: C ++ Streams
Observaciones
El constructor predeterminado de std::istream_iterator construye un iterador que representa el
final de la secuencia. Por lo tanto, std::copy(std::istream_iterator<int>(ifs),
std::istream_iterator<int>(), .... significa copiar desde la posición actual en ifs hasta el final.
Examples
Corrientes de cuerda
std::ostringstream es una clase cuyos objetos se parecen a un flujo de salida (es decir, puede
escribir en ellos a través del operator<< ), pero en realidad almacena los resultados de escritura y
los proporciona en forma de un flujo.
#include <sstream>
#include <string>
int main()
{
ostringstream ss;
ss << "the answer to everything is " << 42;
const string result = ss.str();
}
La línea
ostringstream ss;
crea tal objeto. Este objeto se manipula primero como un flujo regular:
Después de eso, sin embargo, el flujo resultante se puede obtener de esta manera:
Esto es principalmente útil cuando tenemos una clase para la cual se ha definido la serialización
de flujo y para la que queremos una forma de cadena. Por ejemplo, supongamos que tenemos
https://riptutorial.com/es/home 81
alguna clase
class foo
{
// All sort of stuff here.
};
foo f;
podríamos usar
ostringstream ss;
ss << f;
const string result = ss.str();
ifstream tiene el operator bool() , que devuelve verdadero cuando una secuencia no tiene errores
y está lista para leer. Además, ifstream::operator >> devuelve una referencia al flujo mismo, de
modo que podemos leer y verificar EOF (así como errores) en una línea con una sintaxis muy
elegante:
std::ifstream ifs("1.txt");
std::string s;
while(ifs >> s) {
std::cout << s << std::endl;
}
https://riptutorial.com/es/home 82
ifstream::operator >> lee la secuencia hasta que ifstream::operator >> cualquier carácter de
espacio en blanco, por lo que el código anterior imprimirá las palabras de una línea en líneas
separadas. Para leer todo hasta el final de la línea, use std::getline lugar de ifstream::operator
>> . getline devuelve la referencia al hilo con el que trabajó, por lo que está disponible la misma
sintaxis:
while(std::getline(ifs, s)) {
std::cout << s << std::endl;
}
Obviamente, std::getline también debe usarse para leer un archivo de una sola línea hasta el
final.
s.resize(100);
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
s.begin());
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::back_inserter(s));
Alternativamente, es posible inicializar una colección con datos de flujo, usando un constructor
con argumentos de rango de iterador:
std::vector v(std::istreambuf_iterator<char>(ifs),
std::istreambuf_iterator<char>());
Tenga en cuenta que estos ejemplos también son aplicables si ifs se abre como archivo binario:
Copiando arroyos
Un archivo se puede copiar en otro archivo con secuencias e iteradores:
std::ofstream ofs("out.file");
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
https://riptutorial.com/es/home 83
std::ostream_iterator<char>(ofs));
ofs.close();
o redirigido a cualquier otro tipo de flujo con una interfaz compatible. Por ejemplo, el flujo de red
Boost.Asio:
boost::asio::ip::tcp::iostream stream;
stream.connect("example.com", "http");
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::ostream_iterator<char>(stream));
stream.close();
Arrays
Como los iteradores pueden considerarse como una generalización de los punteros, los
contenedores STL en los ejemplos anteriores pueden reemplazarse con matrices nativas. Aquí es
cómo analizar números en una matriz:
int arr[100];
std::copy(std::istream_iterator<char>(ifs), std::istream_iterator<char>(), arr);
Tenga cuidado con el desbordamiento del búfer, ya que las matrices no se pueden redimensionar
sobre la marcha después de que se asignaron. Por ejemplo, si el código anterior se alimenta con
un archivo que contiene más de 100 números enteros, intentará escribir fuera de la matriz y se
ejecutará en un comportamiento indefinido.
Impresión básica
std::ostream_iterator permite imprimir el contenido de un contenedor STL en cualquier flujo de
salida sin ciclos explícitos. El segundo argumento del constructor std::ostream_iterator establece
el delimitador. Por ejemplo, el siguiente código:
std::vector<int> v = {1,2,3,4};
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ! "));
imprimirá
1 ! 2 ! 3 ! 4 !
https://riptutorial.com/es/home 84
ejemplo, sintonicemos std::cout para imprimir valores de punto flotante con 3 dígitos después del
punto decimal:
e instanciar std::ostream_iterator con float , mientras que los valores contenidos permanecen int
:
std::vector<int> v = {1,2,3,4};
std::copy(v.begin(), v.end(), std::ostream_iterator<float>(std::cout, " ! "));
Generación y transformación.
std::generatefunciones std::generate , std::generate_n y std::transform proporcionan una
herramienta muy poderosa para la manipulación de datos sobre la marcha. Por ejemplo, tener un
vector:
std::vector<int> v = {1,2,3,4,8,16};
podemos imprimir fácilmente el valor booleano de "x es par" para cada elemento:
https://riptutorial.com/es/home 85
Arrays
Al igual que en la sección sobre la lectura de archivos de texto, casi todas estas consideraciones
pueden aplicarse a matrices nativas. Por ejemplo, imprimamos valores cuadrados de una matriz
nativa:
Análisis de archivos
std::vector<int> v(100);
std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
v.begin());
std::vector<int> v;
std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
std::back_inserter(v));
Tenga en cuenta que los números en el archivo de entrada se pueden dividir por cualquier
número de caracteres de espacio en blanco y líneas nuevas.
https://riptutorial.com/es/home 86
std::string s;
double a, b;
while(ifs >> a >> b >> s) {
std::cout << a << " " << b << " " << s << std::endl;
}
Transformación
Cualquier función de manipulación de rangos puede usarse con rangos std::istream_iterator .
Uno de ellos es std::transform , que permite procesar datos sobre la marcha. Por ejemplo,
leamos valores enteros, multiplíquelos por 3.14 y almacenemos el resultado en un contenedor de
punto flotante:
std::vector<double> v(100);
std::transform(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
v.begin(),
[](int val) {
return val * 3.14;
});
https://riptutorial.com/es/home 87
Capítulo 15: Campos de bits
Introducción
Los campos de bits empaquetan las estructuras C y C ++ para reducir el tamaño. Esto parece
indoloro: especifique el número de bits para los miembros, y el compilador hace el trabajo de
mezclar bits. La restricción es la incapacidad de tomar la dirección de un miembro de campo de
bit, ya que se almacena de forma combinada. sizeof() también está deshabilitado.
El costo de los campos de bits es un acceso más lento, ya que la memoria debe recuperarse y las
operaciones a nivel de bits deben aplicarse para extraer o modificar los valores de los miembros.
Estas operaciones también se agregan al tamaño ejecutable.
Observaciones
¿Qué tan caras son las operaciones bitwise? Supongamos una estructura de campo no bit
simple:
struct foo {
unsigned x;
unsigned y;
}
static struct foo my_var;
my_var.y = 5;
Esto es sencillo porque x no está mezclado con y. Pero imagina redefinir la estructura con
campos de bits:
struct foo {
unsigned x : 4; /* Range 0-0x0f, or 0 through 15 */
unsigned y : 4;
}
Tanto a x como a y se les asignarán 4 bits, compartiendo un solo byte. Por lo tanto, la estructura
ocupa 1 byte, en lugar de 8. Considere el ensamblaje para establecer y ahora, asumiendo que
termina en el mordisco superior:
https://riptutorial.com/es/home 88
loada register1,#myvar ; get the address of the structure
loadb register2,register1[0] ; get the byte from memory
andb register2,#0x0f ; zero out y in the byte, leaving x alone
orb register2,#0x50 ; put the 5 into the 'y' portion of the byte
stb register1[0],register2 ; put the modified byte back into memory
Esto puede ser una buena compensación si tenemos miles o millones de estas estructuras, y
ayuda a mantener la memoria en la memoria caché o evita el intercambio, o podría inflar el
ejecutable para empeorar estos problemas y demorar el procesamiento. Como con todas las
cosas, usa el buen juicio.
Uso del controlador de dispositivo: evite los campos de bits como una estrategia de
implementación inteligente para los controladores de dispositivo. Los diseños de almacenamiento
de campo de bits no son necesariamente coherentes entre los compiladores, por lo que tales
implementaciones no son portátiles. La lectura-modificación-escritura para establecer valores
puede no hacer lo que los dispositivos esperan, causando comportamientos inesperados.
Examples
Declaración y uso
struct FileAttributes
{
unsigned int ReadOnly: 1;
unsigned int Hidden: 1;
};
Aquí, cada uno de estos dos campos ocupará 1 bit en la memoria. Se especifica mediante : 1
expresión después de los nombres de las variables. El tipo de base del campo de bits podría ser
cualquier tipo integral (int de 8 bits a int de 64 bits). Se recomienda usar el tipo unsigned , de lo
contrario pueden surgir sorpresas.
Si se requieren más bits, reemplace "1" con la cantidad de bits requeridos. Por ejemplo:
struct Date
{
unsigned int Year : 13; // 2^13 = 8192, enough for "year" representation for long time
unsigned int Month: 4; // 2^4 = 16, enough to represent 1-12 month values.
unsigned int Day: 5; // 32
};
Toda la estructura está utilizando tan solo 22 bits, y con la configuración normal del compilador,
sizeof esta estructura sería de 4 bytes.
El uso es bastante simple. Solo declara la variable, y úsala como una estructura ordinaria.
Date d;
d.Year = 2016;
d.Month = 7;
https://riptutorial.com/es/home 89
d.Day = 22;
https://riptutorial.com/es/home 90
Capítulo 16: Categorías de valor
Examples
Significados de la categoría de valor
A las expresiones en C ++ se les asigna una categoría de valor particular, en función del
resultado de esas expresiones. Las categorías de valor para las expresiones pueden afectar la
resolución de sobrecarga de la función C ++.
Las categorías de valor determinan dos propiedades importantes pero separadas de una
expresión. Una propiedad es si la expresión tiene identidad. Una expresión tiene identidad si se
refiere a un objeto que tiene un nombre de variable. Es posible que el nombre de la variable no
esté involucrado en la expresión, pero el objeto aún puede tener uno.
C ++ define otras dos categorías de valores, cada una basada únicamente en una de estas
propiedades: glvalue (expresiones con identidad) y rvalue (expresiones desde las que se puede
mover). Estos actúan como agrupaciones útiles de las categorías anteriores.
prvalue
Una expresión prvalue (pure-rvalue) es una expresión que carece de identidad, cuya evaluación
https://riptutorial.com/es/home 91
se usa normalmente para inicializar un objeto y que se puede mover de forma implícita. Estos
incluyen, pero no se limitan a:
xvalor
Una expresión xvalue (valor eXpiring) es una expresión que tiene identidad y representa un objeto
desde el cual se puede mover implícitamente. La idea general con las expresiones xvalue es que
el objeto que representan se destruirá pronto (de ahí la parte "inspiradora"), y por lo tanto,
mudarse de forma implícita está bien.
Dado:
struct X { int n; };
extern X x;
valor
Una expresión lvalue es una expresión que tiene identidad, pero no se puede mover
implícitamente. Entre ellas se encuentran las expresiones que consisten en un nombre de
variable, un nombre de función, expresiones que son usos de operadores de desreferencia
incorporados y expresiones que se refieren a referencias de valores.
El lvalue típico es simplemente un nombre, pero los lvalues también pueden venir en otros
sabores:
struct X { ... };
X x; // x is an lvalue
X* px = &x; // px is an lvalue
*px = X{}; // *px is also an lvalue, X{} is a prvalue
Además, mientras que la mayoría de los literales (por ejemplo, 4 , 'x' , etc.) son prvalores, los
https://riptutorial.com/es/home 92
literales de cadenas son valores l.
glvalue
Una expresión glvalue (un "lvalue generalizado") es cualquier expresión que tiene identidad,
independientemente de si se puede mover o no. Esta categoría incluye lvalues (expresiones que
tienen identidad pero no se pueden mover) y xvalues (expresiones que tienen identidad y se
pueden mover desde), pero excluye prvalues (expresiones sin identidad).
struct X { int n; };
X foo();
X x;
x; // has a name, so it's a glvalue
std::move(x); // has a name (we're moving from "x"), so it's a glvalue
// can be moved from, so it's an xvalue not an lvalue
valor
Más precisamente, las expresiones rvalue se pueden usar como argumento para una función que
toma un parámetro de tipo T && (donde T es el tipo de expr ). Solo se pueden dar expresiones de
valor como argumentos a tales parámetros de función; Si se usa una expresión sin valor, la
resolución de sobrecarga seleccionará cualquier función que no use un parámetro de referencia
de valor. Y si no existe ninguno, entonces obtienes un error.
La categoría de expresiones rvalue incluye todas las expresiones xvalue y prvalue, y solo esas
expresiones.
La función de biblioteca estándar std::move existe para transformar explícitamente una expresión
sin valor en un valor. Más específicamente, convierte la expresión en un xvalor, ya que incluso si
antes era una expresión de prvalor sin identidad, al pasarla como parámetro a std::move , gana
identidad (el nombre del parámetro de la función) y se convierte en un xvalor.
Considera lo siguiente:
https://riptutorial.com/es/home 93
std::string tiene un constructor que toma un solo parámetro de tipo std::string&& , comúnmente
llamado "constructor de movimiento". Sin embargo, la categoría de valor de la expresión str no es
un rvalue (específicamente es un lvalue), por lo que no puede llamar a esa sobrecarga del
constructor. En su lugar, llama a const std::string& overload, el constructor de copia.
La línea 3 cambia las cosas. El valor de retorno de std::move es un T&& , donde T es el tipo base
del parámetro pasado. Así que std::move(str) devuelve std::string&& . Una llamada de función
cuyo valor de retorno es una referencia rvalue es una expresión rvalue (específicamente un valor
x), por lo que puede llamar al constructor de movimiento de std::string . Después de la línea 3,
str se ha movido desde (los contenidos de quién ahora están indefinidos).
La línea 4 pasa un operador temporal al operador de asignación de std::string . Esto tiene una
sobrecarga que lleva un std::string&& . La expresión std::string("new value") es una expresión
rvalue (específicamente un prvalue), por lo que puede llamar a esa sobrecarga. Por lo tanto, el
temporal se mueve a str , reemplazando los contenidos indefinidos con contenidos específicos.
La línea 5 crea una referencia str_ref llamada str_ref que se refiere a str . Aquí es donde las
categorías de valor se vuelven confusas.
Vea, mientras que str_ref es una referencia de rvalue a std::string , la categoría de valor de la
expresión str_ref no es un valor de r . Es una expresión lvalue. Sí, en serio. Debido a esto, no se
puede llamar al constructor de movimiento de std::string con la expresión str_ref . La línea 6,
por lo tanto, copia el valor de str en test3 .
https://riptutorial.com/es/home 94
Capítulo 17: Clases / Estructuras
Sintaxis
• variable.member_var = constante;
• variable.member_function ();
• variable_pointer-> member_var = constante;
• variable_pointer-> miembro_función ();
Observaciones
Tenga en cuenta que la única diferencia entre las palabras clave struct y class es que, de forma
predeterminada, las variables miembro, las funciones miembro y las clases base de una struct
son public , mientras que en una class son private . Los programadores de C ++ tienden a
llamarlo una clase si tienen constructores y destructores, y la capacidad de imponer sus propios
invariantes; o una estructura si es solo una simple colección de valores, pero el lenguaje C ++ en
sí mismo no hace distinción.
Examples
Conceptos básicos de clase
Una clase es un tipo definido por el usuario. Se introduce una clase con la palabra clave class ,
struct o union . En el uso coloquial, el término "clase" generalmente se refiere solo a las clases no
sindicalizadas.
Las palabras clave class y struct , llamadas claves de clase , son en gran medida
intercambiables, excepto que el especificador de acceso predeterminado para miembros y bases
es "privado" para una clase declarada con la clave de class y "público" para una clase declarada
con la clave struct o union (cf. modificadores de acceso ).
struct Vector
{
int x;
int y;
int z;
};
https://riptutorial.com/es/home 95
// are equivalent to
class Vector
{
public:
int x;
int y;
int z;
};
Al declarar una clase` se agrega un nuevo tipo a su programa, y es posible crear una instancia de
los objetos de esa clase mediante
Vector my_vector;
my_vector.x = 10;
my_vector.y = my_vector.x + 1; // my_vector.y = 11;
my_vector.z = my_vector.y - 4; // my:vector.z = 7;
Especificadores de acceso
Hay tres palabras clave que actúan como especificadores de acceso . Estos limitan el acceso a
los miembros de la clase siguiendo el especificador, hasta que otro especificador cambie de
nuevo el nivel de acceso:
protected Sólo la clase en sí, las clases derivadas y los amigos tienen acceso.
MyStruct s;
s.x = 9; // well formed, because x is public
MyClass c;
c.x = 9; // ill-formed, because x is private
Los especificadores de acceso se usan principalmente para limitar el acceso a los campos y
métodos internos, y obligan al programador a usar una interfaz específica, por ejemplo, para
forzar el uso de captadores y definidores en lugar de hacer referencia a una variable
https://riptutorial.com/es/home 96
directamente:
class MyClass {
public: /* Methods: */
private: /* Fields: */
int m_x;
};
Usar protected es útil para permitir que cierta funcionalidad del tipo solo sea accesible para las
clases derivadas, por ejemplo, en el siguiente código, el método calculateValue() solo es
accesible para las clases derivadas de la clase base Plus2Base , como FortyTwo :
struct Plus2Base {
int value() noexcept { return calculateValue() + 2; }
protected: /* Methods: */
virtual int calculateValue() noexcept = 0;
};
struct FortyTwo: Plus2Base {
protected: /* Methods: */
int calculateValue() noexcept final override { return 40; }
};
Tenga en cuenta que la palabra clave friend se puede usar para agregar excepciones de acceso
a funciones o tipos para acceder a miembros protegidos y privados.
Las palabras clave public , protected y private también se pueden usar para otorgar o limitar el
acceso a subobjetos de clase base. Ver el ejemplo de herencia .
Herencia
Si una clase / estructura B hereda de una clase / estructura A , esto significa que B tiene como
padre A Decimos que B es una clase / estructura derivada de A , y A es la clase / estructura base.
struct A
{
public:
int p1;
protected:
int p2;
private:
int p3;
};
https://riptutorial.com/es/home 97
{
};
• public
• private
• protected
Incluso es posible tener una class derivada de una struct (o viceversa). En este caso, la herencia
predeterminada está controlada por el hijo, por lo que una struct que se deriva de una class se
establecerá de forma predeterminada como herencia pública, y una class que se deriva de una
struct tendrá herencia privada de forma predeterminada.
herencia public :
B b;
b.p1 = 1; //well formed, p1 is public
b.p2 = 1; //ill formed, p2 is protected
b.p3 = 1; //ill formed, p3 is inaccessible
herencia private
struct B : private A
{
void foo()
{
p1 = 0; //well formed, p1 is private in B
p2 = 0; //well formed, p2 is private in B
p3 = 0; //ill formed, p3 is private in A
}
};
B b;
b.p1 = 1; //ill formed, p1 is private
b.p2 = 1; //ill formed, p2 is private
b.p3 = 1; //ill formed, p3 is inaccessible
herencia protected :
struct B : protected A
{
https://riptutorial.com/es/home 98
void foo()
{
p1 = 0; //well formed, p1 is protected in B
p2 = 0; //well formed, p2 is protected in B
p3 = 0; //ill formed, p3 is private in A
}
};
B b;
b.p1 = 1; //ill formed, p1 is protected
b.p2 = 1; //ill formed, p2 is protected
b.p3 = 1; //ill formed, p3 is inaccessible
Tenga en cuenta que aunque se permite la herencia protected , el uso real de la misma es raro.
Una instancia de cómo se usa la herencia protected en la aplicación es en la especialización de
clase base parcial (generalmente denominada "polimorfismo controlado").
Cuando la POO era relativamente nueva, se decía con frecuencia que la herencia (pública)
modelaba una relación "IS-A". Es decir, la herencia pública es correcta solo si una instancia de la
clase derivada es también una instancia de la clase base.
Más tarde, esto se refinó en el Principio de Sustitución de Liskov : la herencia pública solo debe
usarse cuando / si una instancia de la clase derivada puede sustituirse por una instancia de la
clase base bajo cualquier circunstancia posible (y aún tiene sentido).
En general, se dice que la herencia privada incorpora una relación completamente diferente: "se
implementa en términos de" (a veces se denomina relación "HAS-A"). Por ejemplo, una clase de
Stack podría heredar de forma privada de una clase Vector . La herencia privada tiene una
similitud mucho mayor con la agregación que con la herencia pública.
La herencia protegida casi nunca se usa, y no hay un acuerdo general sobre qué tipo de relación
abarca.
Herencia virtual
struct A{};
struct B: public virtual A{};
Cuando la clase B tiene una base virtual A , significa que A residirá en la mayoría de la clase
derivada del árbol de herencia y, por lo tanto, la mayoría de la clase derivada también es
responsable de inicializar esa base virtual:
struct A
{
int member;
A(int param)
{
member = param;
}
};
https://riptutorial.com/es/home 99
struct B: virtual A
{
B(): A(5){}
};
struct C: B
{
C(): /*A(88)*/ {}
};
void f()
{
C object; //error since C is not initializing it's indirect virtual base `A`
}
También tenga en cuenta que cuando estamos creando un object variable, la mayoría de la clase
derivada es C , por lo que C es responsable de crear (llamar al constructor de) A y, por lo tanto, el
valor de A::member es 88 , no 5 (como lo sería si fuésemos creando objeto de tipo B ).
A A A
/ \ | |
B C B C
\ / \ /
D D
virtual inheritance normal inheritance
La herencia virtual resuelve este problema: como la base virtual reside solo en la mayoría de los
objetos derivados, solo habrá una instancia de A en D
struct A
{
void foo() {}
};
https://riptutorial.com/es/home 100
Eliminar los comentarios resuelve la ambigüedad.
Herencia múltiple
class A {};
class B : public A {};
class A {};
class B {};
class C : public A, public B {};
Nota: esto puede generar ambigüedad si se usan los mismos nombres en varias class o
struct heredadas. ¡Ten cuidado!
La herencia múltiple puede ser útil en ciertos casos pero, a veces, algún tipo de problema se
encuentra mientras se usa la herencia múltiple.
Por ejemplo: dos clases base tienen funciones con el mismo nombre que no se anulan en la clase
derivada y si escribe código para acceder a esa función utilizando el objeto de la clase derivada,
el compilador muestra error porque no puede determinar a qué función llamar. Aquí hay un código
para este tipo de ambigüedad en herencia múltiple.
class base1
{
public:
void funtion( )
{ //code for base1 function }
};
class base2
{
void function( )
{ // code for base2 function }
};
};
int main()
{
derived obj;
https://riptutorial.com/es/home 101
}
Pero, este problema se puede resolver utilizando la función de resolución de alcance para
especificar qué función se debe clasificar como base1 o base2:
int main()
{
obj.base1::function( ); // Function of class base1 is called.
obj.base2::function( ); // Function of class base2 is called.
}
Para acceder a las variables y funciones miembro de un objeto de una clase, el . el operador es
usado:
struct SomeStruct {
int a;
int b;
void foo() {}
};
SomeStruct var;
// Accessing member variable a in var.
std::cout << var.a << std::endl;
// Assigning member variable b in var.
var.b = 1;
// Calling a member function.
var.foo();
Cuando se accede a los miembros de una clase a través de un puntero, el operador -> se usa
comúnmente. Alternativamente, la instancia puede ser anulada y la . Operador utilizado, aunque
esto es menos común:
struct SomeStruct {
int a;
int b;
void foo() {}
};
SomeStruct var;
SomeStruct *p = &var;
// Accessing member variable a in var via pointer.
std::cout << p->a << std::endl;
std::cout << (*p).a << std::endl;
// Assigning member variable b in var via pointer.
p->b = 1;
(*p).b = 1;
// Calling a member function via a pointer.
p->foo();
(*p).foo();
https://riptutorial.com/es/home 102
miembro estático desde una instancia o un puntero a una instancia usando el . o -> operador,
respectivamente, con la misma sintaxis que para acceder a miembros no estáticos.
struct SomeStruct {
int a;
int b;
void foo() {}
static int c;
static void bar() {}
};
int SomeStruct::c;
SomeStruct var;
SomeStruct* p = &var;
// Assigning static member variable c in struct SomeStruct.
SomeStruct::c = 5;
// Accessing static member variable c in struct SomeStruct, through var and p.
var.a = var.c;
var.b = p->c;
// Calling a static member function.
SomeStruct::bar();
var.bar();
p->bar();
Fondo
El operador -> es necesario porque el operador de acceso miembro . Tiene prioridad sobre el
operador de desreferenciación * .
Uno esperaría que *pa desreferenciara p (resultando en una referencia al objeto p que apunta) y
luego accediendo a su miembro a . Pero, de hecho, intenta acceder al miembro a de p y luego
desreferenciarlo. Ie *pa es equivalente a *(pa) . En el ejemplo anterior, esto daría como resultado
un error del compilador debido a dos hechos: Primero, p es un puntero y no tiene un miembro a .
En segundo lugar, a es un número entero y, por lo tanto, no se puede eliminar la referencia.
La solución poco común a este problema sería controlar explícitamente la prioridad: (*p).a
En cambio, el operador -> casi siempre se usa. Es una mano corta para desreferenciar primero el
puntero y luego acceder a él. Ie (*p).a es exactamente lo mismo que p->a .
El operador :: es el operador de alcance, que se utiliza de la misma manera que para acceder a
un miembro de un espacio de nombres. Esto se debe a que se considera que un miembro de la
clase estática está dentro del alcance de esa clase, pero no se considera un miembro de las
instancias de esa clase. El uso de lo normal . y -> también está permitido para miembros
estáticos, a pesar de que no sean miembros de instancia, por razones históricas; esto es útil para
escribir código genérico en plantillas, ya que la persona que llama no necesita preocuparse de si
una función miembro dada es estática o no estática.
https://riptutorial.com/es/home 103
class A {
public:
int move();
int turn();
};
class B : private A {
public:
using A::turn;
};
B b;
b.move(); // compile error
b.turn(); // OK
Este enfoque evita de manera eficiente el acceso a los métodos públicos A mediante el envío al
puntero o referencia A:
B b;
A& a = static_cast<A&>(b); // compile error
En el caso de la herencia pública, dicha conversión proporcionará acceso a todos los métodos
públicos A, a pesar de que existen formas alternativas de evitar esto en B derivada, como la
ocultación:
class B : public A {
private:
int move();
};
o privado utilizando:
class B : public A {
private:
using A::move;
};
B b;
A& a = static_cast<A&>(b); // OK for public inheritance
a.move(); // OK
C ++ 11
Derivar una clase puede estar prohibido con especificador final . Vamos a declarar una clase
final:
class A final {
};
https://riptutorial.com/es/home 104
Ahora cualquier intento de subclase causará un error de compilación:
class A {
};
// OK.
class B final : public A {
};
Amistad
La palabra clave friend se utiliza para otorgar acceso a otras clases y funciones a miembros
privados y protegidos de la clase, incluso a través de su definición fuera del alcance de la clase.
class Animal{
private:
double weight;
double height;
public:
friend void printWeight(Animal animal);
friend class AnimalPrinter;
// A common use for a friend function is to overload the operator<< for streaming.
friend std::ostream& operator<<(std::ostream& os, Animal animal);
};
class AnimalPrinter
{
public:
void print(const Animal& animal)
{
// Because of the `friend class AnimalPrinter;" declaration, we are
// allowed to access private members here.
std::cout << animal.weight << ", " << animal.height << std::endl;
}
}
https://riptutorial.com/es/home 105
int main() {
Animal animal = {10, 5};
printWeight(animal);
AnimalPrinter aPrinter;
aPrinter.print(animal);
10
10, 5
Animal height: 5
Una class o struct también puede contener otra definición de class / struct dentro de sí misma,
que se denomina "clase anidada"; en esta situación, la clase contenedora se conoce como la
"clase adjunta". La definición de clase anidada se considera un miembro de la clase adjunta, pero
por lo demás es separada.
struct Outer {
struct Inner { };
};
Desde fuera de la clase adjunta, se accede a las clases anidadas mediante el operador de
alcance. Sin embargo, desde el interior de la clase adjunta, las clases anidadas se pueden usar
sin calificadores:
struct Outer {
struct Inner { };
Inner in;
};
// ...
Outer o;
Outer::Inner i = o.in;
Al igual que con una class / struct no anidada, las funciones miembro y las variables estáticas se
pueden definir ya sea dentro de una clase anidada o en el espacio de nombres adjunto. Sin
embargo, no se pueden definir dentro de la clase adjunta, debido a que se considera que es una
clase diferente a la clase anidada.
// Bad.
struct Outer {
struct Inner {
void do_something();
};
void Inner::do_something() {}
https://riptutorial.com/es/home 106
};
// Good.
struct Outer {
struct Inner {
void do_something();
};
};
void Outer::Inner::do_something() {}
Al igual que con las clases no anidadas, las clases anidadas se pueden declarar y definir
posteriormente, siempre que se definan antes de usarlas directamente.
class Outer {
class Inner1;
class Inner2;
Inner1 in1;
Inner2* in2p;
public:
Outer();
~Outer();
};
C ++ 11
Antes de C ++ 11, las clases anidadas solo tenían acceso a nombres de tipo, miembros static y
enumeradores de la clase adjunta; todos los demás miembros definidos en la clase adjunta
estaban fuera de los límites.
C ++ 11
A partir de C ++ 11, las clases anidadas y sus miembros se tratan como si fueran friend de la
clase adjunta y pueden acceder a todos sus miembros, de acuerdo con las reglas de acceso
habituales; si los miembros de la clase anidada requieren la capacidad de evaluar uno o más
miembros no estáticos de la clase adjunta, por lo tanto, se les debe pasar una instancia:
class Outer {
struct Inner {
int get_sizeof_x() {
return sizeof(x); // Legal (C++11): x is unevaluated, so no instance is required.
}
https://riptutorial.com/es/home 107
int get_x() {
return x; // Illegal: Can't access non-static member without an instance.
}
int get_x(Outer& o) {
return o.x; // Legal (C++11): As a member of Outer, Inner can access private
members.
}
};
int x;
};
A la inversa, la clase adjunta no se trata como un amigo de la clase anidada y, por lo tanto, no
puede acceder a sus miembros privados sin un permiso explícito.
class Outer {
class Inner {
// friend class Outer;
int x;
};
Inner in;
public:
int get_x() {
return in.x; // Error: int Outer::Inner::x is private.
// Uncomment "friend" line above to fix.
}
};
Los amigos de una clase anidada no se consideran automáticamente amigos de la clase adjunta;
Si también necesitan ser amigos de la clase adjunta, esto debe declararse por separado. A la
inversa, como la clase adjunta no se considera automáticamente un amigo de la clase anidada,
tampoco se considerarán amigos de la clase anexa amigos de la clase anidada.
class Outer {
friend void barge_out(Outer& out, Inner& in);
class Inner {
friend void barge_in(Outer& out, Inner& in);
int i;
};
int o;
};
https://riptutorial.com/es/home 108
int o = out.o; // Good.
}
Al igual que con todos los demás miembros de la clase, las clases anidadas solo pueden
nombrarse desde fuera de la clase si tienen acceso público. Sin embargo, puede acceder a ellos
sin importar el modificador de acceso, siempre y cuando no los nombre explícitamente.
class Outer {
struct Inner {
void func() { std::cout << "I have no private taboo.\n"; }
};
public:
static Inner make_Inner() { return Inner(); }
};
// ...
También puede crear un alias de tipo para una clase anidada. Si un alias de tipo está contenido
en la clase adjunta, el tipo anidado y el alias de tipo pueden tener diferentes modificadores de
acceso. Si el alias de tipo está fuera de la clase adjunta, requiere que la clase anidada, o un
typedef misma, sea pública.
class Outer {
class Inner_ {};
public:
typedef Inner_ Inner;
};
// ...
Al igual que con otras clases, las clases anidadas pueden derivar o derivarse de otras clases.
struct Outer {
struct Inner : Base {};
};
https://riptutorial.com/es/home 109
Esto puede ser útil en situaciones en las que la clase adjunta se deriva de otra clase, al permitir
que el programador actualice la clase anidada según sea necesario. Esto se puede combinar con
un typedef para proporcionar un nombre coherente para cada clase anidada de la clase
envolvente:
class BaseOuter {
struct BaseInner_ {
virtual void do_something() {}
virtual void do_something_else();
} b_in;
public:
typedef BaseInner_ Inner;
void BaseOuter::BaseInner_::do_something_else() {}
// ---
public:
typedef DerivedInner_ Inner;
void DerivedOuter::DerivedInner_::do_something_else() {}
// ...
// Calls BaseOuter::BaseInner_::do_something();
BaseOuter* b = new BaseOuter;
BaseOuter::Inner& bin = b->getInner();
bin.do_something();
b->getInner().do_something();
// Calls DerivedOuter::DerivedInner_::do_something();
BaseOuter* d = new DerivedOuter;
BaseOuter::Inner& din = d->getInner();
din.do_something();
d->getInner().do_something();
En el caso anterior, tanto BaseOuter como DerivedOuter suministran el tipo de miembro Inner , como
BaseInner_ y DerivedInner_ , respectivamente. Esto permite que los tipos anidados se deriven sin
romper la interfaz de la clase envolvente, y permite que el tipo anidado se utilice de forma
polimórfica.
https://riptutorial.com/es/home 110
Tipos de miembros y alias
Una class o struct también puede definir alias de tipo de miembro, que son alias de tipo
contenidos dentro de la clase y tratados como miembros de la misma clase.
struct IHaveATypedef {
typedef int MyTypedef;
};
struct IHaveATemplateTypedef {
template<typename T>
using MyTemplateTypedef = std::vector<T>;
};
Al igual que los miembros estáticos, se accede a estos typedefs usando el operador de alcance,
:: .
IHaveATypedef::MyTypedef i = 5; // i is an int.
IHaveATemplateTypedef::MyTemplateTypedef<int> v; // v is a std::vector<int>.
Al igual que con los alias de tipo normal, cada alias de tipo miembro puede referirse a cualquier
tipo definido o con alias antes, pero no después, de su definición. Del mismo modo, un typedef
fuera de la definición de clase puede referirse a cualquier typedef accesible dentro de la definición
de clase, siempre que venga después de la definición de clase.
template<typename T>
struct Helper {
T get() const { return static_cast<T>(42); }
};
struct IHaveTypedefs {
// typedef MyTypedef NonLinearTypedef; // Error if uncommented.
typedef int MyTypedef;
typedef Helper<MyTypedef> MyTypedefHelper;
};
Los alias de tipo de miembro se pueden declarar con cualquier nivel de acceso y respetarán el
modificador de acceso apropiado.
class TypedefAccessLevels {
typedef int PrvInt;
protected:
typedef int ProInt;
public:
typedef int PubInt;
https://riptutorial.com/es/home 111
};
Esto se puede usar para proporcionar un nivel de abstracción, permitiendo que el diseñador de
una clase cambie su funcionamiento interno sin romper el código que se basa en él.
class Something {
friend class SomeComplexType;
short s;
// ...
public:
typedef SomeComplexType MyHelper;
// ...
};
// ...
Something s;
Something::MyHelper hlp = s.get_helper();
En esta situación, si la clase auxiliar se cambia de SomeComplexType a algún otro tipo, solo será
necesario modificar el typedef y la declaración de friend ; Siempre que la clase auxiliar
proporcione la misma funcionalidad, cualquier código que lo use como Something::MyHelper lugar
de especificarlo por su nombre, por lo general funcionará sin ninguna modificación. De esta
manera, minimizamos la cantidad de código que debe modificarse cuando se cambia la
implementación subyacente, de modo que el nombre de tipo solo necesita cambiarse en una
ubicación.
class SomethingElse {
AnotherComplexType<bool, int, SomeThirdClass> helper;
public:
typedef decltype(helper) MyHelper;
private:
InternalVariable<MyHelper> ivh;
// ...
https://riptutorial.com/es/home 112
public:
MyHelper& get_helper() const { return helper; }
// ...
};
Como con todo, sin embargo, esto puede ser llevado demasiado lejos. Si el nombre de tipo solo
se usa una o dos veces internamente y cero veces externamente, por ejemplo, no es necesario
proporcionar un alias para él. Si se usa cientos o miles de veces a lo largo de un proyecto, o si
tiene un nombre lo suficientemente largo, entonces puede ser útil proporcionarlo como un typedef
en lugar de usarlo siempre en términos absolutos. Hay que equilibrar la compatibilidad y la
conveniencia con la cantidad de ruido innecesario creado.
Esto también se puede utilizar con clases de plantilla, para proporcionar acceso a los parámetros
de la plantilla desde fuera de la clase.
template<typename T>
class SomeClass {
// ...
public:
typedef T MyParam;
MyParam getParam() { return static_cast<T>(42); }
};
template<typename T>
typename T::MyParam some_func(T& t) {
return t.getParam();
}
SomeClass<int> si;
int i = some_func(si);
Esto se usa comúnmente con los contenedores, que normalmente proporcionarán su tipo de
elemento, y otros tipos de ayuda, como alias de tipo de miembro. La mayoría de los contenedores
en la biblioteca estándar de C ++, por ejemplo, proporcionan los siguientes 12 tipos de ayuda,
junto con cualquier otro tipo especial que puedan necesitar.
template<typename T>
class SomeContainer {
// ...
public:
// Let's provide the same helper types as most standard containers.
typedef T value_type;
typedef std::allocator<value_type> allocator_type;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef value_type* pointer;
https://riptutorial.com/es/home 113
typedef const value_type* const_pointer;
typedef MyIterator<value_type> iterator;
typedef MyConstIterator<value_type> const_iterator;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
};
Antes de C ++ 11, también se usaba comúnmente para proporcionar un tipo de "plantilla typedef ",
ya que la característica aún no estaba disponible; se han vuelto un poco menos comunes con la
introducción de plantillas de alias, pero siguen siendo útiles en algunas situaciones (y se
combinan con plantillas de alias en otras situaciones, lo que puede ser muy útil para obtener
componentes individuales de un tipo complejo, como un puntero de función). ). Comúnmente
usan el type nombre para su alias de tipo.
template<typename T>
struct TemplateTypedef {
typedef T type;
}
TemplateTypedef<int>::type i; // i is an int.
Esto se usaba a menudo con tipos con múltiples parámetros de plantilla, para proporcionar un
alias que define uno o más de los parámetros.
template<typename T>
struct MonoDisplayLine {
typedef Array<T, 80, 1> type;
};
Una clase también puede tener miembros static , que pueden ser variables o funciones. Se
considera que estos están dentro del alcance de la clase, pero no se tratan como miembros
normales; tienen una duración de almacenamiento estático (existen desde el inicio del programa
hasta el final), no están vinculados a una instancia particular de la clase y solo existe una copia
https://riptutorial.com/es/home 114
para toda la clase.
class Example {
static int num_instances; // Static data member (static member variable).
int i; // Non-static member variable.
public:
static std::string static_str; // Static data member (static member variable).
static int static_func(); // Static member function.
int Example::num_instances;
std::string Example::static_str = "Hello.";
// ...
Las variables miembro estáticas no se consideran definidas dentro de la clase, solo se declaran, y
por lo tanto tienen su definición fuera de la definición de la clase; el programador puede, pero no
es obligatorio, inicializar variables estáticas en su definición. Al definir las variables miembro, se
omite la palabra clave static .
class Example {
static int num_instances; // Declaration.
public:
static std::string static_str; // Declaration.
// ...
};
Debido a esto, las variables estáticas pueden ser tipos incompletos (aparte del void ), siempre
que se definan más adelante como un tipo completo.
struct ForwardDeclared;
class ExIncomplete {
static ForwardDeclared fd;
static ExIncomplete i_contain_myself;
static int an_array[];
};
https://riptutorial.com/es/home 115
struct ForwardDeclared {};
ForwardDeclared ExIncomplete::fd;
ExIncomplete ExIncomplete::i_contain_myself;
int ExIncomplete::an_array[5];
Las funciones miembro estáticas se pueden definir dentro o fuera de la definición de clase, como
ocurre con las funciones miembro normales. Al igual que con las variables miembro estáticas, la
palabra clave static se omite al definir funciones miembro estáticas fuera de la definición de
clase.
public:
static int static_func() { return num_instances; }
// ...
// Or...
Si una variable miembro estática se declara const pero no es volatile , y es de tipo integral o de
enumeración, se puede inicializar en declaración, dentro de la definición de clase.
enum E { VAL = 5 };
struct ExConst {
const static int ci = 5; // Good.
static const E ce = VAL; // Good.
const static double cd = 5; // Error.
static const volatile int cvi = 5; // Error.
C ++ 11
A partir de C ++ 11, las variables miembro estáticas de tipos LiteralType (tipos que pueden
construirse en tiempo de compilación, de acuerdo con constexpr reglas constexpr ) también
pueden declararse como constexpr ; si es así, deben inicializarse dentro de la definición de clase.
https://riptutorial.com/es/home 116
struct ExConstexpr {
constexpr static int ci = 5; // Good.
static constexpr double cd = 5; // Good.
constexpr static int carr[] = { 1, 1, 2 }; // Good.
static constexpr ConstexprConstructibleClass c{}; // Good.
constexpr static int bad_ci; // Error.
};
Si una variable de miembro estática const o constexpr se usa de forma estándar (de manera
informal, si tiene su dirección tomada o está asignada a una referencia), todavía debe tener una
definición separada, fuera de la definición de clase. Esta definición no puede contener un
inicializador.
struct ExODR {
static const int odr_used = 5;
};
const int* odr_user = & ExODR::odr_used; // Error; uncomment above line to resolve.
Como los miembros estáticos no están vinculados a una instancia determinada, se puede acceder
a ellos utilizando el operador de alcance, :: .
También se puede acceder a ellos como si fueran miembros normales, no estáticos. Esto es de
importancia histórica, pero se usa con menos frecuencia que el operador de alcance para evitar
confusiones sobre si un miembro es estático o no estático.
Example ex;
std::string rts = ex.static_str;
Los miembros de la clase pueden acceder a miembros estáticos sin calificar su alcance, al igual
que con los miembros de clase no estáticos.
class ExTwo {
static int num_instances;
int my_num;
public:
ExTwo() : my_num(num_instances++) {}
int ExTwo::num_instances;
https://riptutorial.com/es/home 117
No pueden ser mutable , ni deberían serlo; como no están vinculados a ninguna instancia dada, si
una instancia es o no const no afecta a los miembros estáticos.
struct ExDontNeedMutable {
int immuta;
mutable int muta;
static int i;
// ...
Los miembros estáticos respetan los modificadores de acceso, al igual que los miembros no
estáticos.
class ExAccess {
static int prv_int;
protected:
static int pro_int;
public:
static int pub_int;
};
int ExAccess::prv_int;
int ExAccess::pro_int;
int ExAccess::pub_int;
// ...
Como no están vinculados a una instancia determinada, las funciones miembro estáticas no
tienen this puntero; Debido a esto, no pueden acceder a las variables miembro no estáticas a
menos que se pase una instancia.
class ExInstanceRequired {
int i;
public:
ExInstanceRequired() : i(0) {}
https://riptutorial.com/es/home 118
static void good_mutate(ExInstanceRequired& e) { ++e.i *= 5; } // Good.
};
struct ExPointer {
void nsfunc() {}
static void sfunc() {}
};
Debido a no tener un this puntero, sino que también no pueden ser const o volatile , ni pueden
tener Ref-calificadores. Tampoco pueden ser virtuales.
struct ExCVQualifiersAndVirtual {
static void func() {} // Good.
static void cfunc() const {} // Error.
static void vfunc() volatile {} // Error.
static void cvfunc() const volatile {} // Error.
static void rfunc() & {} // Error.
static void rvfunc() && {} // Error.
Como no están vinculados a una instancia determinada, las variables miembro estáticas se tratan
efectivamente como variables globales especiales; se crean cuando se inicia el programa y se
destruyen cuando se sale, independientemente de si existe alguna instancia de la clase. Solo
existe una copia de cada variable miembro estática (a menos que la variable se declare
thread_local (C ++ 11 o posterior), en cuyo caso hay una copia por subproceso).
Las variables miembro estáticas tienen el mismo enlace que la clase, ya sea que la clase tenga
enlace externo o interno. Las clases locales y las clases sin nombre no tienen permitido tener
miembros estáticos.
Una clase puede tener funciones miembro no estáticas , que operan en instancias individuales de
la clase.
class CL {
public:
void member_function() {}
};
https://riptutorial.com/es/home 119
Estas funciones se llaman en una instancia de la clase, como así:
CL instance;
instance.member_function();
struct ST {
void defined_inside() {}
void defined_outside();
};
void ST::defined_outside() {}
Pueden ser calificados para CV y / o ref , lo que afecta la forma en que ven la instancia a la que
se les solicita; la función considerará que la instancia tiene el calificador (es) cv especificado, si
corresponde. La versión que se llame se basará en los calificadores cv de la instancia. Si no hay
una versión con los mismos calificadores de CV que la instancia, se llamará una versión más
calificada de CV, si está disponible.
struct CVQualifiers {
void func() {} // 1: Instance is non-cv-qualified.
void func() const {} // 2: Instance is const.
CVQualifiers non_cv_instance;
const CVQualifiers c_instance;
C ++ 11
struct RefQualifiers {
void func() & {} // 1: Called on normal instances.
void func() && {} // 2: Called on rvalue (temporary) instances.
};
RefQualifiers rf;
rf.func(); // Calls #1.
RefQualifiers{}.func(); // Calls #2.
struct BothCVAndRef {
https://riptutorial.com/es/home 120
void func() const& {} // Called on normal instances. Sees instance as const.
void func() && {} // Called on temporary instances.
};
También pueden ser virtuales ; esto es fundamental para el polimorfismo, y permite que una (s)
clase (s) infantil (es) proporcione la misma interfaz que la clase primaria, al tiempo que
proporciona su propia funcionalidad.
struct Base {
virtual void func() {}
};
struct Derived {
virtual void func() {}
};
void foo()
{
struct /* No name */ {
float x;
float y;
} point;
point.x = 42;
}
struct Circle
{
struct /* No name */ {
float x;
float y;
} center; // but a member name
float radius;
};
y después
Circle circle;
circle.center.x = 42.f;
https://riptutorial.com/es/home 121
struct InvalidCircle
{
struct /* No name */ {
float centerX;
float centerY;
}; // No member either.
float radius;
};
C ++ 11
• lamdba puede ser visto como una struct especial sin nombre .
decltype(circle.point) otherPoint;
• La instancia de struct sin nombre puede ser un parámetro del método de plantilla:
void print_square_coordinates()
{
const struct {float x; float y;} points[] = {
{-1, -1}, {-1, 1}, {1, -1}, {1, 1}
};
https://riptutorial.com/es/home 122
Capítulo 18: Clasificación
Observaciones
La familia de funciones std::sort se encuentra en la biblioteca de algorithm .
Examples
Clasificación de contenedores de secuencia con orden específico
C ++ 11
#include <vector>
#include <algorithm>
#include <functional>
std::vector<int> v = {5,1,2,4,3};
// Or just:
std::sort(v.begin(), v.end());
//Or just:
std::sort(v.rbegin(), v.rend());
C ++ 14
https://riptutorial.com/es/home 123
Podemos sobrecargar a este operador para que la llamada de sort predeterminada funcione en
tipos definidos por el usuario.
class Base {
public:
int variable;
};
int main() {
std::vector <Base> vector;
std::deque <Base> deque;
std::list <Base> list;
deque.push_back(a);
deque.push_back(b);
list.push_back(a);
list.push_back(b);
return 0;
}
https://riptutorial.com/es/home 124
// Include sequence containers
#include <vector>
#include <deque>
#include <list>
class Base {
public:
int variable;
};
int main() {
std::vector <Base> vector;
std::deque <Base> deque;
std::list <Base> list;
deque.push_back(a);
deque.push_back(b);
list.push_back(a);
list.push_back(b);
return 0;
}
C ++ 11
https://riptutorial.com/es/home 125
#include <forward_list>
class Base {
public:
int variable;
};
int main() {
// Create 2 elements to sort
Base a(10);
Base b(5);
return 0;
}
La función de comparación debe imponer un ordenamiento estricto y débil en los elementos. Una
simple comparación menor que (o mayor que) será suficiente.
C ++ 11
https://riptutorial.com/es/home 126
#include <vector>
#include <algorithm>
std::sort requiere que sus iteradores sean iteradores de acceso aleatorio. Los contenedores de
secuencias std::list y std::forward_list (que requieren C ++ 11) no proporcionan iteradores de
acceso aleatorio, por lo que no pueden usarse con std::sort . Sin embargo, tienen funciones de
miembro de sort que implementan un algoritmo de clasificación que funciona con sus propios
tipos de iteradores.
C ++ 11
#include <list>
#include <algorithm>
Sus funciones de sort miembros siempre ordenan la lista completa, por lo que no pueden ordenar
un sub-rango de elementos. Sin embargo, dado que list y forward_list tienen operaciones de
empalme rápidas, puede extraer los elementos que se ordenarán de la lista, ordenarlos y luego
volver a guardarlos donde fueron más eficientemente así:
Este ejemplo ordena los elementos en orden ascendente de una clave utilizando un mapa.
Puede usar cualquier tipo, incluida la clase, en lugar de std::string , en el siguiente ejemplo.
#include <iostream>
#include <utility>
#include <map>
int main()
{
std::map<double, std::string> sorted_map;
// Sort the names of the planets according to their size
sorted_map.insert(std::make_pair(0.3829, "Mercury"));
https://riptutorial.com/es/home 127
sorted_map.insert(std::make_pair(0.9499, "Venus"));
sorted_map.insert(std::make_pair(1, "Earth"));
sorted_map.insert(std::make_pair(0.532, "Mars"));
sorted_map.insert(std::make_pair(10.97, "Jupiter"));
sorted_map.insert(std::make_pair(9.14, "Saturn"));
sorted_map.insert(std::make_pair(3.981, "Uranus"));
sorted_map.insert(std::make_pair(3.865, "Neptune"));
Salida:
Si son posibles entradas con claves iguales, use el multimap lugar del map (como en el siguiente
ejemplo).
Para ordenar los elementos de forma descendente , declare el mapa con un funtor de
comparación adecuado ( std::greater<> ):
#include <iostream>
#include <utility>
#include <map>
int main()
{
std::multimap<int, std::string, std::greater<int>> sorted_map;
// Sort the names of animals in descending order of the number of legs
sorted_map.insert(std::make_pair(6, "bug"));
sorted_map.insert(std::make_pair(4, "cat"));
sorted_map.insert(std::make_pair(100, "centipede"));
sorted_map.insert(std::make_pair(2, "chicken"));
sorted_map.insert(std::make_pair(0, "fish"));
sorted_map.insert(std::make_pair(4, "horse"));
sorted_map.insert(std::make_pair(8, "spider"));
Salida
https://riptutorial.com/es/home 128
spider (has 8 legs)
bug (has 6 legs)
cat (has 4 legs)
horse (has 4 legs)
chicken (has 2 legs)
fish (has 0 legs)
El algoritmo de sort ordena una secuencia definida por dos iteradores. Esto es suficiente para
ordenar una matriz integrada (también conocida como c-style).
C ++ 11
C ++ 11
https://riptutorial.com/es/home 129
Capítulo 19: Comparaciones lado a lado de
ejemplos clásicos de C ++ resueltos a través
de C ++ vs C ++ 11 vs C ++ 14 vs C ++ 17
Examples
Buceando a través de un contenedor
Aunque simples, tales escritos están sujetos a errores semánticos comunes, como un operador
de comparación incorrecto o una variable de indexación incorrecta:
El bucle también se puede lograr para todos los contenedores que utilizan iteradores, con
inconvenientes similares:
C ++ 11 introdujo rango basado en bucles y palabras clave auto , permitiendo que el código se
convierta en:
for(auto& x : c) x = 0;
Aquí los únicos parámetros son el contenedor c y una variable x para mantener el valor actual.
Esto evita los errores semánticos apuntados previamente.
En dicha implementación, la expresión auto begin = c.begin(), end = c.end(); las fuerzas begin y
end para ser del mismo tipo, mientras que el end nunca se incrementa, ni se elimina la referencia.
Por lo tanto, el bucle for basado en rango solo funciona para contenedores definidos por un par
iterador / iterador. El estándar C ++ 17 relaja esta restricción al cambiar la implementación a:
https://riptutorial.com/es/home 130
auto begin = c.begin();
auto end = c.end();
for(; begin != end; ++begin)
{
// ...
}
Aquí se permite que el begin y el end sean de diferentes tipos, siempre que se puedan comparar
por desigualdad. Esto permite recorrer más contenedores, por ejemplo, un contenedor definido
por un par iterador / centinela.
https://riptutorial.com/es/home 131
Capítulo 20: Compilando y construyendo
Introducción
Los programas escritos en C ++ deben compilarse antes de que puedan ejecutarse. Hay una gran
variedad de compiladores disponibles dependiendo de su sistema operativo.
Observaciones
La mayoría de los sistemas operativos se envían sin un compilador, y deben instalarse más
adelante. Algunas opciones comunes de compiladores son:
Por favor, consulte el manual del compilador apropiado, sobre cómo compilar un programa C ++.
Otra opción para usar un compilador específico con su propio sistema de compilación específico,
es posible permitir que los sistemas de compilación genéricos configuren el proyecto para un
compilador específico o para el instalado por defecto.
Examples
Compilando con GCC
Asumiendo un único archivo fuente llamado main.cpp , el comando para compilar y vincular un
ejecutable no optimizado es el siguiente (Compilar sin optimización es útil para el desarrollo inicial
y la depuración, aunque oficialmente se recomienda -Og para las versiones más recientes de
GCC).
Para producir un ejecutable optimizado para su uso en la producción, utilizar una de las -O
opciones (véase: -O1 , -O2 , -O3 , -Os , -Ofast ):
Si se omite la opción -O, se utiliza -O0, que significa que no hay optimizaciones, como valor
predeterminado (la especificación de -O sin un número se resuelve en -O1).
https://riptutorial.com/es/home 132
g++ -o app -Wall -O2 -ftree-partial-pre main.cpp
Cualquiera de los anteriores producirá un archivo binario que puede ejecutarse con .\app.exe en
Windows y ./app en Linux, Mac OS, etc.
La bandera -o también se puede omitir. En este caso, GCC creará una salida ejecutable
predeterminada a.exe en Windows y a.out en sistemas similares a Unix. Para compilar un archivo
sin vincularlo, use la opción -c :
Esto produce un archivo de objeto llamado file.o que luego puede vincularse con otros archivos
para producir un binario:
La bandera -Wall habilita advertencias para muchos errores comunes y siempre debe usarse.
Para mejorar la calidad del código, a menudo también se recomienda utilizar -Wextra y otros
indicadores de advertencia que no están habilitados automáticamente por -Wall y -Wextra .
GCC incluye algunas extensiones específicas del compilador que están deshabilitadas cuando
entran en conflicto con un estándar especificado por el -std= . Para compilar con todas las
extensiones habilitadas, se puede utilizar el valor gnu++XX , donde XX es cualquiera de los años
utilizados por los valores de c++ enumerados anteriormente.
Tenga en cuenta que debido a errores en GCC, el indicador -pthread debe estar presente en la
https://riptutorial.com/es/home 133
compilación y enlace para que GCC admita la funcionalidad de subprocesamiento estándar C ++
introducida con C ++ 11, como std::thread y std::wait_for . Omitirlo cuando se usan funciones de
subprocesamiento puede generar advertencias pero resultados no válidos en algunas
plataformas.
O deje que el enlazador determine el pedido a través de --start-group y --end-group (nota: esto
tiene un costo de rendimiento significativo):
Para los programadores que vienen de GCC o Clang a Visual Studio, o los programadores que se
sienten más cómodos con la línea de comandos en general, puede usar el compilador de Visual C
++ desde la línea de comandos así como el IDE.
Si desea compilar su código desde la línea de comandos en Visual Studio, primero debe
configurar el entorno de línea de comandos. Esto se puede hacer abriendo la línea de Visual
Studio Command Prompt / Developer Command Prompt / x86 Native Tools Command Prompt / x64 Native
Tools Command Prompt o similar (según lo provisto por su versión de Visual Studio), o en la línea de
comandos, navegando a el subdirectorio VC directorio de instalación del compilador (normalmente
\Program Files (x86)\Microsoft Visual Studio x\VC , donde x es el número de versión (como 10.0
para 2010 o 14.0 para 2015) y ejecuta el archivo por lotes VCVARSALL con una parámetro de línea
de comando especificado aquí .
Tenga en cuenta que a diferencia de GCC, Visual Studio no proporciona un front-end para el
https://riptutorial.com/es/home 134
vinculador ( link.exe ) a través del compilador ( cl.exe ), sino que proporciona el vinculador como
un programa separado, al que el compilador llama cuando sale. cl.exe y link.exe se pueden usar
por separado con diferentes archivos y opciones, o se puede indicar a cl que pase archivos y
opciones para link si ambas tareas se realizan juntas. Cualquier opción de enlace especificada
para cl se convertirá en opciones para link , y cualquier archivo que no sea procesado por cl
pasará directamente al link . Como esta es principalmente una guía simple para compilar con la
línea de comandos de Visual Studio, los argumentos para el link no se describirán en este
momento; Si necesita una lista, vea aquí .
Tenga en cuenta que los argumentos para cl distinguen entre mayúsculas y minúsculas, mientras
que los argumentos para link no lo son.
[Tenga en cuenta que algunos de los siguientes ejemplos utilizan la variable "directorio actual" del
shell de Windows, %cd% , al especificar nombres de ruta absolutos. Para cualquier persona que no
esté familiarizada con esta variable, se expande al directorio de trabajo actual. Desde la línea de
comandos, será el directorio en el que estaba cuando ejecutó cl , y se especifica en el símbolo
del sistema de manera predeterminada (si su símbolo del sistema es C:\src> , por ejemplo,
entonces %cd% es C:\src\ ).]
Suponiendo que un solo archivo de origen denominado main.cpp en la carpeta actual, el comando
para compilar y vincular un ejecutable no optimizado (útil para el desarrollo inicial y la depuración)
es (use uno de los siguientes):
cl main.cpp
// Generates object file "main.obj".
// Performs linking with "main.obj".
// Generates executable "main.exe".
cl /Od main.cpp
// Same as above.
// "/Od" is the "Optimisation: disabled" option, and is the default when no /O is specified.
cl main.cpp niam.cpp
// Generates object files "main.obj" and "niam.obj".
// Performs linking with "main.obj" and "niam.obj".
// Generates executable "main.exe".
cl main.cpp src\*.cpp
// Generates object file "main.obj", plus one object file for each ".cpp" file in folder
// "%cd%\src".
// Performs linking with "main.obj", and every additional object file generated.
// All object files will be in the current folder.
// Generates executable "main.exe".
https://riptutorial.com/es/home 135
cl /o name main.cpp
// Generates executable named "name.exe".
cl /o folder\ main.cpp
// Generates executable named "main.exe", in folder "%cd%\folder".
cl /o folder\name main.cpp
// Generates executable named "name.exe", in folder "%cd%\folder".
cl /Fename main.cpp
// Same as "/o name".
cl /Fefolder\ main.cpp
// Same as "/o folder\".
cl /Fefolder\name main.cpp
// Same as "/o folder\name".
Tanto /o como /Fe pasan su parámetro (llamémoslo o-param ) para link como /OUT:o-param ,
agregando la extensión apropiada (generalmente .exe o .dll ) para "nombrar" o-param según sea
necesario. Mientras tanto /o y /Fe son, en mi opinión, idénticas en funcionalidad, esta última es la
preferida para Visual Studio. /o está marcado como obsoleto y parece que se proporciona
principalmente para programadores más familiarizados con GCC o Clang.
De manera similar, para producir un ejecutable optimizado (para uso en producción), use:
cl /O1 main.cpp
// Optimise for executable size. Produces small programs, at the possible expense of slower
// execution.
cl /O2 main.cpp
// Optimise for execution speed. Produces fast programs, at the possible expense of larger
// file size.
// If compiling for x64, and LINK doesn't automatically detect target platform:
https://riptutorial.com/es/home 136
cl main.cpp /link /machine:X64
Cualquiera de los anteriores producirá un ejecutable con el nombre especificado por /o o /Fe , o si
no se proporciona ninguno, con un nombre idéntico al primer archivo de origen u objeto
especificado para el compilador.
cl /o yo zp.obj pz.cpp
// Generates "yo.exe".
cl /c main.cpp
// Generates object file "main.obj".
Esto le dice a cl que salga sin llamar al link , y produce un archivo objeto, que luego puede
vincularse con otros archivos para producir un binario.
cl main.obj niam.cpp
// Generates object file "niam.obj".
// Performs linking with "main.obj" and "niam.obj".
// Generates executable "main.exe".
También hay otros parámetros de línea de comando valiosos, que sería muy útil para los usuarios
saber:
cl /EHsc main.cpp
// "/EHsc" specifies that only standard C++ ("synchronous") exceptions will be caught,
// and `extern "C"` functions will not throw exceptions.
// This is recommended when writing portable, platform-independent code.
cl /clr main.cpp
// "/clr" specifies that the code should be compiled to use the common language runtime,
// the .NET Framework's virtual machine.
// Enables the use of Microsoft's C++/CLI language in addition to standard ("native") C++,
// and creates an executable that requires .NET to run.
cl /Za main.cpp
// "/Za" specifies that Microsoft extensions should be disabled, and code should be
// compiled strictly according to ISO C++ specifications.
// This is recommended for guaranteeing portability.
https://riptutorial.com/es/home 137
cl /Zi main.cpp
// "/Zi" generates a program database (PDB) file for use when debugging a program, without
// affecting optimisation specifications, and passes the option "/DEBUG" to LINK.
cl /LD dll.cpp
// "/LD" tells CL to configure LINK to generate a DLL instead of an executable.
// LINK will output a DLL, in addition to an LIB and EXP file for use when linking.
// To use the DLL in other programs, pass its associated LIB to CL or LINK when compiling
those
// programs.
Para cualquiera que esté más familiarizado con los sistemas * nix y / o GCC / Clang, cl , link y
otras herramientas de línea de comandos de Visual Studio puede aceptar parámetros
especificados con un guión (como -c ) en lugar de una barra (como /c ). Además, Windows
reconoce una barra diagonal o una barra diagonal inversa como un separador de ruta válido, por
lo que también se pueden usar las rutas estilo * nix. Esto facilita la conversión de líneas de
comando del compilador simple de g++ o clang++ a cl , o viceversa, con cambios mínimos.
Por supuesto, cuando se transfieren líneas de comando que usan opciones más complejas de g++
o clang++ , debe buscar comandos equivalentes en la documentación del compilador
correspondiente y / o en los sitios de recursos, pero esto hace que sea más fácil comenzar con un
tiempo mínimo de aprendizaje. Nuevos compiladores.
En caso de que necesite funciones de idioma específicas para su código, se requiere una versión
específica de MSVC. Desde Visual C ++ 2015 Update 3 , es posible elegir la versión del estándar
para compilar a través de la bandera /std . Los valores posibles son /std:c++14 y /std:c++latest (
/std:c++17 seguirá pronto).
https://riptutorial.com/es/home 138
4. Haga clic en Plantillas -> Visual C ++ -> Aplicación de consola Win32 y luego nombre el
proyecto MyFirstProgram .
https://riptutorial.com/es/home 139
7. Marque la casilla Empty project y luego haga clic en Finalizar:
https://riptutorial.com/es/home 140
8. Haga clic derecho en la carpeta Archivo de origen y luego -> Agregar -> Nuevo elemento:
https://riptutorial.com/es/home 141
9. Seleccione Archivo C ++ y nombre el archivo main.cpp, luego haga clic en Agregar:
https://riptutorial.com/es/home 142
10: Copie y pegue el siguiente código en el nuevo archivo main.cpp:
#include <iostream>
int main()
{
std::cout << "Hello World!\n";
return 0;
}
https://riptutorial.com/es/home 143
11. Haga clic en Depurar -> Iniciar sin depurar (o presione ctrl + F5):
https://riptutorial.com/es/home 144
Compilando con Clang
Como la interfaz de Clang está diseñada para ser compatible con GCC, la mayoría de los
programas que se pueden compilar a través de GCC se compilarán cuando intercambies g++ por
clang++ en los scripts de compilación. Si no se -std=version , se utilizará gnu11.
Los usuarios de Windows que están acostumbrados a MSVC pueden intercambiar cl.exe con
clang-cl.exe . De forma predeterminada, clang intenta ser compatible con la versión más alta de
MSVC que se ha instalado.
En el caso de compilar con visual studio, clang-cl puede usarse cambiando el Platform toolset de
Platform toolset la Platform toolset en las propiedades del proyecto.
En ambos casos, clang solo es compatible a través de su interfaz, aunque también intenta
generar archivos de objetos compatibles binarios. Los usuarios de clang-cl deben tener en cuenta
que la compatibilidad con MSVC aún no está completa .
Para usar clang o clang-cl, uno podría usar la instalación predeterminada en ciertas distribuciones
de Linux o las que vienen con IDE (como XCode en Mac). Para otras versiones de este
compilador o en plataformas que no lo tienen instalado, se puede descargar desde la página de
descarga oficial .
Si está utilizando CMake para compilar su código, generalmente puede cambiar el compilador
configurando las variables de entorno CC y CXX esta manera:
mkdir build
cd build
CC=clang CXX=clang++ cmake ..
cmake --build .
https://riptutorial.com/es/home 145
Compiladores en linea
Varios sitios web proporcionan acceso en línea a compiladores de C ++. El conjunto de funciones
del compilador en línea varía significativamente de un sitio a otro, pero generalmente permiten
hacer lo siguiente:
El comportamiento del sitio web del compilador en línea suele ser bastante restrictivo, ya que
permite que cualquier persona ejecute compiladores y ejecute código arbitrario en su servidor,
mientras que la ejecución de código arbitrario a distancia se considera una vulnerabilidad.
Los compiladores en línea pueden ser útiles para los siguientes propósitos:
• Ejecute un pequeño fragmento de código desde una máquina que carece de compilador de
C ++ (teléfonos inteligentes, tabletas, etc.).
• Asegúrese de que el código se compile correctamente con diferentes compiladores y se
ejecute de la misma manera, independientemente del compilador con el que se compiló.
• Aprender o enseñar conceptos básicos de C ++.
• Conozca las funciones modernas de C ++ (C ++ 14 y C ++ 17 en un futuro próximo) cuando
el compilador C ++ actualizado no esté disponible en la máquina local.
• Detecta un error en tu compilador en comparación con un gran conjunto de otros
compiladores. Compruebe si se solucionó un error del compilador en futuras versiones, que
no están disponibles en su máquina.
• Resolver problemas de jueces en línea.
• Desarrolle aplicaciones completas (incluso pequeñas) utilizando C ++. Por lo general, los
compiladores en línea no permiten enlazar con bibliotecas de terceros o descargar
artefactos de compilación.
• Realizar cálculos intensivos. Los recursos informáticos del lado del servidor son limitados,
por lo que cualquier programa proporcionado por el usuario se eliminará después de unos
segundos de ejecución. El tiempo de ejecución permitido suele ser suficiente para la prueba
y el aprendizaje.
• Atacar el servidor del compilador o cualquier otro host de terceros en la red.
Ejemplos:
https://riptutorial.com/es/home 146
comando. Proporciona compiladores de GCC y Clang para su uso.
• http://cpp.sh/ - Compilador en línea con soporte para C ++ 14. No le permite editar la línea
de comandos del compilador, pero algunas opciones están disponibles a través de los
controles GUI.
• https://gcc.godbolt.org/ : proporciona una amplia lista de versiones de compilador,
arquitecturas y resultados de desensamblaje. Muy útil cuando necesitas inspeccionar lo que
compila tu código por diferentes compiladores. GCC, Clang, MSVC ( CL ), compilador Intel (
icc ), ELLCC y Zapcc están presentes, con uno o más de estos compiladores disponibles
para ARM, ARMv8 (como ARM64), AVR de Atmel, MIPS, MIPS64, MSP430, PowerPC ,
x86, y x64 architecutres. Los argumentos de la línea de comando del compilador pueden ser
editados.
• https://ideone.com/ : se utiliza ampliamente en la red para ilustrar el comportamiento del
fragmento de código. Proporciona GCC y Clang para su uso, pero no le permite editar la
línea de comandos del compilador.
• http://melpon.org/wandbox - Admite numerosas versiones de compilador Clang y GNU /
GCC.
• http://onlinegdb.com/ : un IDE extremadamente minimalista que incluye un editor, un
compilador (gcc) y un depurador (gdb).
• http://rextester.com/ : proporciona compiladores de Clang, GCC y Visual Studio para C y C
++ (junto con compiladores para otros idiomas), con la biblioteca Boost disponible para su
uso.
• http://tutorialspoint.com/compile_cpp11_online.php : shell UNIX con todas las funciones con
GCC y un explorador de proyectos fácil de usar.
• http://webcompiler.cloudapp.net/ - Compilador en línea de Visual Studio 2015,
proporcionado por Microsoft como parte de RiSE4fun.
https://riptutorial.com/es/home 147
4. El archivo de código de objeto producido por el ensamblador está vinculado entre sí
con los archivos de código de objeto para cualquier función de biblioteca utilizada para
producir una biblioteca o un archivo ejecutable.
Preprocesamiento
Después de todo esto, el preprocesador produce una salida única que es una secuencia de
tokens resultantes de las transformaciones descritas anteriormente. También agrega algunos
marcadores especiales que le dicen al compilador de dónde proviene cada línea para que pueda
usarlos para producir mensajes de error razonables.
Algunos errores pueden producirse en esta etapa con un uso inteligente de las directivas #if y
#error.
g++ -E prog.cpp
Compilacion
Los archivos de objetos pueden referirse a símbolos que no están definidos. Este es el caso
cuando usa una declaración y no proporciona una definición para ella. Al compilador no le importa
esto, y felizmente producirá el archivo de objeto siempre que el código fuente esté bien formado.
Los compiladores generalmente le permiten detener la compilación en este punto. Esto es muy
útil porque con él puede compilar cada archivo de código fuente por separado. La ventaja que
esto proporciona es que no necesita recompilar todo si solo cambia un solo archivo.
https://riptutorial.com/es/home 148
Los archivos de objetos producidos se pueden colocar en archivos especiales denominados
bibliotecas estáticas, para luego reutilizarlos más fácilmente.
Es en esta etapa que se informan los errores "regulares" del compilador, como los errores de
sintaxis o los errores de resolución de sobrecarga fallidos.
Para detener el proceso después del paso de compilación, podemos usar la opción -S:
Montaje
El ensamblador crea código objeto. En un sistema UNIX puede ver archivos con un sufijo .o (.OBJ
en MSDOS) para indicar archivos de código de objeto. En esta fase, el ensamblador convierte
esos archivos de objeto de código de ensamblaje en instrucciones a nivel de máquina y el archivo
creado es un código de objeto reubicable. Por lo tanto, la fase de compilación genera el programa
de objeto reubicable y este programa se puede utilizar en diferentes lugares sin tener que
compilar nuevamente.
Para detener el proceso después del paso de ensamblaje, puede usar la opción -c:
Enlace
El enlazador es lo que produce la salida de compilación final de los archivos de objetos que
produjo el ensamblador. Esta salida puede ser una biblioteca compartida (o dinámica) (y aunque
el nombre es similar, no tienen mucho en común con las bibliotecas estáticas mencionadas
anteriormente) o un archivo ejecutable.
Vincula todos los archivos de objetos reemplazando las referencias a símbolos no definidos con
las direcciones correctas. Cada uno de estos símbolos se puede definir en otros archivos de
objetos o en bibliotecas. Si están definidas en bibliotecas distintas de la biblioteca estándar, debe
informar al vinculador sobre ellas.
En esta etapa, los errores más comunes son las definiciones faltantes o las definiciones
duplicadas. Lo primero significa que las definiciones no existen (es decir, no están escritas), o que
los archivos de objetos o las bibliotecas donde residen no se entregaron al vinculador. Lo último
es obvio: el mismo símbolo se definió en dos archivos de objetos o bibliotecas diferentes.
1. Descargue e instale Code :: Blocks aquí . Si está en Windows, tenga cuidado de seleccionar
un archivo cuyo nombre contenga mingw , los otros archivos no instalarán ningún compilador.
https://riptutorial.com/es/home 149
https://riptutorial.com/es/home 150
3. Seleccione "Aplicación de consola" y haga clic en "Ir":
4. Haga clic en "Siguiente", seleccione "C ++", haga clic en "Siguiente", seleccione un nombre
para su proyecto y elija una carpeta para guardarlo, haga clic en "Siguiente" y luego haga
clic en "Finalizar".
5. Ahora puedes editar y compilar tu código. Un código predeterminado que imprime "¡Hola
mundo!" En la consola ya está ahí. Para compilar y / o ejecutar su programa, presione uno
de los tres botones de compilar / ejecutar en la barra de herramientas:
https://riptutorial.com/es/home 151
https://riptutorial.com/es/home 152
Para compilar sin correr, pulsa , para correr sin compilar nuevamente, presione y
para compilar y luego correr, presiona .
https://riptutorial.com/es/home 153
Lea Compilando y construyendo en línea:
https://riptutorial.com/es/cplusplus/topic/4708/compilando-y-construyendo
https://riptutorial.com/es/home 154
Capítulo 21: Comportamiento definido por la
implementación
Examples
Char puede estar sin firmar o firmado
El estándar no especifica si char debe estar firmado o sin firmar. Diferentes compiladores lo
implementan de manera diferente, o podrían permitir cambiarlo usando un interruptor de línea de
comando.
• char
• Tipos enteros firmados
• Tipos de enteros sin signo
• char16_t y char32_t
• bool
• wchar_t
Tamaño de char
Todas las versiones de estándar el C ++ especifican, en § 5.3.3.1, que sizeof rendimientos 1 para
unsigned char , signed char , y char (es definido por la implementación si el char tipo se signed o
unsigned ).
C ++ 14
chares lo suficientemente grande como para representar 256 valores diferentes, para ser
adecuado para almacenar unidades de código UTF-8.
https://riptutorial.com/es/home 155
El estándar especifica, en el § 3.9.1.2, que en la lista de tipos enteros con signo estándar , que
constan de caracteres con signed char , short int , int , long int long long int , cada tipo
proporcionará al menos tanto almacenamiento como los anteriores en la lista. Además, como se
especifica en el § 3.9.1.3, cada uno de estos tipos tiene un tipo de entero sin signo estándar
correspondiente, unsigned char unsigned short int unsigned int , unsigned short int unsigned int ,
unsigned int unsigned long int , unsigned long int unsigned long long int , que tiene el mismo
tamaño y alineación que Su correspondiente tipo firmado. Además, como se especifica en el §
3.9.1.1, char tiene los mismos requisitos de tamaño y alineación que el signed char y el unsigned
char .
C ++ 11
Antes de C ++ 11, long long y unsigned long long no formaban parte oficialmente del estándar C
++. Sin embargo, después de su introducción a C, en C99, muchos compiladores admitieron long
long como un tipo entero con signo extendido , y unsigned long long como un tipo entero sin signo
extendido , con las mismas reglas que los tipos C.
C ++ 11
Los tamaños mínimos específicos para cada tipo no están dados por la norma. En su lugar, cada
tipo tiene un rango mínimo de valores que puede admitir, que, como se especifica en § 3.9.1.3, se
hereda del estándar C, en §5.2.4.2.1. El tamaño mínimo de cada tipo se puede inferir
aproximadamente de este rango, al determinar el número mínimo de bits requeridos; tenga en
cuenta que para cualquier plataforma dada, el rango real admitido de cualquier tipo puede ser
mayor que el mínimo. Tenga en cuenta que para los tipos con signo, los rangos corresponden al
complemento de uno, no al complemento de dos de uso más común; esto es para permitir que
una gama más amplia de plataformas cumpla con el estándar.
Bits mínimos
Tipo Rango mínimo
requeridos
unsigned
short 0 a 65,535 (0 a 2 16 - 1) dieciséis
https://riptutorial.com/es/home 156
Bits mínimos
Tipo Rango mínimo
requeridos
-2,147,483,647 a 2,147,483,647 (- (2 31 - 1) a (2
signed long
31 - 1)) 32
unsigned long
long 0 a 18,446,744,073,709,551,615 (0 a 2 64 - 1) 64
Como se permite que cada tipo sea mayor que su requisito de tamaño mínimo, los tipos pueden
diferir en tamaño entre las implementaciones. El ejemplo más notable de esto es con los modelos
de datos de 64 bits LP64 y LLP64, donde los sistemas LLP64 (tales como Windows de 64 bits)
tiene 32 bits ints y long s, y sistemas de LP64 (tales como Linux de 64 bits) tienen int s de 32 bits
y s de 64 bits de long . Debido a esto, no se puede suponer que los tipos de enteros tengan un
ancho fijo en todas las plataformas.
C ++ 11
Si se requieren tipos de enteros con ancho fijo, use tipos del encabezado <cstdint> , pero tenga
en cuenta que el estándar hace que las implementaciones sean compatibles con los tipos de
ancho exacto int8_t , int16_t , int32_t , int64_t , intptr_t , uint8_t , uint16_t , uint32_t , uint64_t y
uintptr_t .
C ++ 11
Los tamaños de char16_t y char32_t están definidos por la implementación, como se especifica en
el § 5.3.3.1, con las estipulaciones que figuran en el § 3.9.1.5:
https://riptutorial.com/es/home 157
Tamaño de bool
Tamaño de wchar_t
wchar_t , como se especifica en § 3.9.1.5, es un tipo distinto, cuyo rango de valores puede
representar cada unidad de código distinta del conjunto de caracteres extendido más grande
entre los locales admitidos. Tiene el mismo tamaño, firmeza y alineación que uno de los otros
tipos integrales, que se conoce como su tipo subyacente . El tamaño de este tipo está definido por
la implementación, como se especifica en el § 5.3.3.1, y puede ser, por ejemplo, al menos 8, 16 o
32 bits; si un sistema admite Unicode, por ejemplo, se requiere que wchar_t tenga al menos 32
bits (una excepción a esta regla es Windows, donde wchar_t es de 16 bits por motivos de
compatibilidad). Se hereda de la norma C90, ISO 9899: 1990 § 4.1.5, con solo una pequeña
redacción.
• En sistemas similares a Unix y Unix, wchar_t es de 32 bits, y generalmente se usa para UTF-
32.
• En Windows, wchar_t es de 16 bits y se usa para UTF-16.
• En un sistema que solo tiene soporte de 8 bits, wchar_t es de 8 bits.
C ++ 11
Si se desea compatibilidad con Unicode, se recomienda usar char para UTF-8, char16_t para UTF-
16 o char32_t para UTF-32, en lugar de usar wchar_t .
Modelos de datos
Como se mencionó anteriormente, los anchos de los tipos de enteros pueden diferir entre
plataformas. Los modelos más comunes son los siguientes, con tamaños especificados en bits:
ILP32 (4/4/4) 32 32 32
LLP64 (4/4/8) 32 32 64
LP64 (4/8/8) 32 64 64
https://riptutorial.com/es/home 158
Fuera de estos modelos:
Tenga en cuenta, sin embargo, que estos modelos no se mencionan específicamente en la norma
en sí.
En C ++, un byte es el espacio ocupado por un objeto char . La cantidad de bits en un byte viene
dada por CHAR_BIT , que se define en climits y se requiere que sea al menos 8. Mientras que la
mayoría de los sistemas modernos tienen bytes de 8 bits, y POSIX requiere que CHAR_BIT sea
exactamente 8, hay algunos sistemas donde CHAR_BIT es mayor que 8, es decir, un solo byte
puede estar compuesto por 8, 16, 32 o 64 bits.
int x = 42;
int* p = &x;
long addr = reinterpret_cast<long>(p);
std::cout << addr << "\n"; // prints some numeric address,
// probably in the architecture's native address format
Del mismo modo, el puntero obtenido por conversión de un entero también está definido por la
implementación.
La forma correcta de almacenar un puntero como un entero es usando los tipos uintptr_t o
intptr_t :
uintptr_t uip;
C ++ 11
std::uintptr_t uip;
https://riptutorial.com/es/home 159
un tipo de entero sin signo con la propiedad de que cualquier puntero válido para void
se puede convertir a este tipo, luego se puede volver a convertir en puntero a void , y
el resultado se comparará igual al puntero original.
Si bien, para la mayoría de las plataformas modernas, puede asumir un espacio de direcciones
plano y que la aritmética en uintptr_t es equivalente a la aritmética en char * , es totalmente
posible que una implementación realice una transformación al uintptr_t void * a uintptr_t
siempre que la transformación pueda se invierte cuando se devuelve desde uintptr_t a void * .
Tecnicismos
• En los sistemas intptr_t XSI (X / Open System Interfaces), se requieren los tipos intptr_t y
uintptr_t , de lo contrario son opcionales .
• En el sentido del estándar C, las funciones no son objetos; el estándar C no garantiza que
uintptr_t pueda contener un puntero de función. De todos modos, la conformidad con
POSIX (2.12.3) requiere que:
• C99 §7.18.1:
uintptr_tpodría tener sentido si quiere hacer cosas a los bits del puntero que no puede
hacer con sensatez con un entero con signo.
Los rangos de los tipos de enteros están definidos por la implementación. El encabezado <limits>
proporciona la plantilla std::numeric_limits<T> que proporciona los valores mínimo y máximo de
todos los tipos fundamentales. Los valores satisfacen las garantías proporcionadas por el
estándar C a través de los <climits> y (> = C ++ 11) <cinttypes> .
https://riptutorial.com/es/home 160
• std::numeric_limits<int>::min() es igual a INT_MIN , que es menor o igual que -32767.
• std::numeric_limits<int>::max() es igual a INT_MAX , que es mayor o igual a 32767.
• std::numeric_limits<unsigned int>::max() es igual a UINT_MAX , que es mayor o igual a 65535.
• std::numeric_limits<long>::min() es igual a LONG_MIN , que es menor o igual a -2147483647.
• std::numeric_limits<long>::max() es igual a LONG_MAX , que es mayor o igual que 2147483647.
• std::numeric_limits<unsigned long>::max() es igual a ULONG_MAX , que es mayor o igual que
4294967295.
C ++ 11
Para los tipos de punto flotante T , max() es el valor finito máximo, mientras que min() es el valor
normalizado positivo mínimo. Se proporcionan miembros adicionales para los tipos de punto
flotante, que también están definidos por la implementación pero satisfacen ciertas garantías
proporcionadas por el estándar C a través del encabezado <cfloat> .
El estándar requiere que el long double proporcione al menos la misma precisión que el double , lo
que proporciona al menos la misma precisión que el float ; y que un long double puede
representar cualquier valor que un double pueda representar, mientras que un double puede
https://riptutorial.com/es/home 161
representar cualquier valor que un float pueda representar. Los detalles de la representación
están, sin embargo, definidos por la implementación.
Cuando un entero con signo o sin signo se convierte en un tipo de entero con signo y su valor no
se puede representar en el tipo de destino, el valor producido se define por la implementación.
Ejemplo:
// Suppose that on this implementation, the range of signed char is -128 to +127 and
// the range of unsigned char is 0 to 255
int x = 12345;
signed char sc = x; // sc has an implementation-defined value
unsigned char uc = x; // uc is initialized to 57 (i.e., 12345 modulo 256)
enum E {
RED,
GREEN,
BLUE,
};
using T = std::underlying_type<E>::type; // implementation-defined
Sin embargo, el estándar requiere que el tipo subyacente de una enumeración no sea mayor que
int menos que int y unsigned int no puedan representar todos los valores de la enumeración. Por
lo tanto, en el código anterior, T podría ser int , unsigned int , o short , pero no long long , para dar
algunos ejemplos.
Tenga en cuenta que una enumeración tiene el mismo tamaño (según lo devuelto por sizeof ) que
su tipo subyacente.
https://riptutorial.com/es/home 162
Capítulo 22: Comportamiento indefinido
Introducción
¿Qué es el comportamiento indefinido (UB)? De acuerdo con la norma ISO C ++ (§1.3.24,
N4296), es un "comportamiento por el que esta norma internacional no impone requisitos".
Esto significa que cuando un programa se encuentra con UB, se le permite hacer lo que quiera. A
menudo, esto significa un choque, pero puede que simplemente no haga nada, haga que los
demonios salgan volando por tu nariz , ¡o incluso parece que funciona correctamente!
No hace falta decir que debes evitar escribir código que invoque a UB.
Observaciones
Si un programa contiene un comportamiento indefinido, el estándar de C ++ no impone
restricciones a su comportamiento.
• Puede parecer que funciona según lo previsto por el desarrollador, pero también puede
fallar o producir resultados extraños.
• El comportamiento puede variar entre ejecuciones del mismo programa.
• Cualquier parte del programa puede funcionar mal, incluidas las líneas que vienen antes de
la línea que contiene un comportamiento indefinido.
• La implementación no es necesaria para documentar el resultado de un comportamiento
indefinido.
• La mayoría de los compiladores tienen marcas de advertencia para advertir sobre algunos
casos de comportamiento indefinido en tiempo de compilación.
https://riptutorial.com/es/home 163
• Las versiones más recientes de gcc y clang incluyen un indicador denominado
"Desinfectante de comportamiento indefinido" ( -fsanitize=undefined ) que verificará el
comportamiento indefinido en el tiempo de ejecución, a un costo de rendimiento.
• lint herramientas similares a lint pueden realizar un análisis de comportamiento indefinido
más completo.
De la sección 1.9 (Ejecución del programa) de la norma C ++ 14 (ISO / IEC 14882: 2014):
Examples
Leer o escribir a través de un puntero nulo.
Aunque esto causa con mayor frecuencia un fallo de segmentación, no está definido y puede
pasar cualquier cosa.
https://riptutorial.com/es/home 164
nulo
Omitir la declaración de return en una función que tiene un tipo de retorno que no es void es un
comportamiento indefinido .
int function() {
// Missing return statement
}
int main() {
function(); //Undefined Behavior
}
Nota: main es la única excepción a la regla. Si main no tiene una declaración de return , el
compilador inserta automáticamente return 0; Para ti, para que puedas dejarlo fuera de forma
segura.
C ++ 11
"hello world" es una cadena literal, por lo que modificarlo da un comportamiento indefinido.
C ++ 11
Es un comportamiento indefinido acceder a un índice que está fuera de los límites de una
matriz (o el contenedor de la biblioteca estándar para esa materia, ya que todos se implementan
utilizando una matriz sin procesar ):
https://riptutorial.com/es/home 165
int array[] = {1, 2, 3, 4, 5};
array[5] = 0; // Undefined behavior
Se permite tener un puntero que apunta al final de la matriz (en este caso, array + 5 ),
simplemente no se puede eliminar la referencia, ya que no es un elemento válido.
const int *end = array + 5; // Pointer to one past the last index
for (int *p = array; p != end; ++p)
// Do something with `p`
La división por 0 está definida matemáticamente y, como tal, tiene sentido que se trate de un
comportamiento indefinido.
Sin embargo:
int x = INT_MAX + 1;
Este es uno de los más desagradables, ya que por lo general produce un comportamiento
reproducible y sin interrupciones, por lo que los desarrolladores pueden verse tentados a confiar
en gran medida en el comportamiento observado.
https://riptutorial.com/es/home 166
// x is 0
Los enteros sin signo, declarados sin firmar, obedecerán las leyes del módulo
aritmético 2^n donde n es el número de bits en la representación del valor de ese
tamaño particular de entero.
(C ++ 11 párrafo 3.9.1 / 4)
signed int x ;
if(x > x + 1)
{
//do something
}
Aquí, dado que no se define un desbordamiento de enteros con signo, el compilador es libre de
asumir que nunca puede suceder y, por lo tanto, puede optimizar el bloque "if"
int a;
std::cout << a; // Undefined behavior!
Aunque es muy poco probable en la práctica (ya que depende del soporte de hardware
específico) el compilador podría electrocutar al programador al compilar el ejemplo de código
anterior. Con un compilador y un soporte de hardware de este tipo, tal respuesta al
comportamiento indefinido aumentaría notablemente el entendimiento promedio (vivo) del
programador del verdadero significado del comportamiento indefinido, que es que el estándar no
impone ninguna restricción al comportamiento resultante.
C ++ 14
https://riptutorial.com/es/home 167
• el operando derecho del operador de asignación, si el operando izquierdo también es de
tipo unsigned char ;
• el inicializador para un objeto unsigned char ;
Tenga en cuenta que una variable static siempre se inicializa con cero (si es posible):
static int a;
std::cout << a; // Defined behavior, 'a' is 0
Si una clase, enumeración, función en línea, plantilla o miembro de una plantilla tiene un enlace
externo y se define en múltiples unidades de traducción, todas las definiciones deben ser
idénticas o el comportamiento no está definido según la Regla de una definición (ODR) .
foo.h :
class Foo {
public:
double x;
private:
int y;
};
Foo get_foo();
foo.cpp :
#include "foo.h"
Foo get_foo() { /* implementation */ }
main.cpp :
// I want access to the private member, so I am going to replace Foo with my own type
class Foo {
public:
double x;
int y;
};
Foo get_foo(); // declare this function ourselves since we aren't including foo.h
int main() {
Foo foo = get_foo();
// do something with foo.y
}
https://riptutorial.com/es/home 168
Emparejamiento incorrecto de la asignación de memoria y desasignación
Un objeto solo puede ser desasignado por delete si fue asignado por new y no es una matriz. Si el
argumento para delete no fue devuelto por new o es una matriz, el comportamiento no está
definido.
Un objeto solo puede ser desasignado por delete[] si fue asignado por new y es una matriz. Si el
argumento para delete[] no fue devuelto por new o no es una matriz, el comportamiento no está
definido.
int* p3 = static_cast<int*>(malloc(sizeof(int)));
free(p3); // correct
// delete p3; // undefined
// delete[] p3; // undefined
Dichos problemas se pueden evitar evitando completamente malloc y programas free en C ++,
prefiriendo los punteros inteligentes de la biblioteca estándar sobre new y delete en bruto, y
prefiriendo std::vector y std::string sobre new y delete[] .
En la mayoría de los casos, es ilegal acceder a un objeto de un tipo como si fuera un tipo
diferente (sin tener en cuenta los calificadores cv). Ejemplo:
float x = 42;
int y = reinterpret_cast<int&>(x);
• Se puede acceder a un objeto de tipo de clase como si fuera de un tipo que es una clase
base del tipo de clase real.
• Se puede acceder a cualquier tipo como char o unsigned char , pero lo contrario no es cierto:
no se puede acceder a una matriz char como si fuera un tipo arbitrario.
• Se puede acceder a un tipo entero con signo como el tipo sin signo correspondiente y
viceversa .
Una regla relacionada es que si se llama a una función miembro no estática en un objeto que en
https://riptutorial.com/es/home 169
realidad no tiene el mismo tipo que la clase que define la función, o una clase derivada, entonces
ocurre un comportamiento indefinido. Esto es cierto incluso si la función no accede al objeto.
struct Base {
};
struct Derived : Base {
void f() {}
};
struct Unrelated {};
Unrelated u;
Derived& r1 = reinterpret_cast<Derived&>(u); // ok
r1.f(); // UB
Base b;
Derived& r2 = reinterpret_cast<Derived&>(b); // ok
r2.f(); // UB
Si una operación aritmética que produce un tipo de punto flotante produce un valor que no está en
el rango de valores representables del tipo de resultado, el comportamiento no está definido de
acuerdo con el estándar C ++, pero puede definirse por otros estándares que la máquina pueda
cumplir. tales como IEEE 754.
float x = 1.0;
for (int i = 0; i < 10000; i++) {
x *= 10.0; // will probably overflow eventually; undefined behavior
}
De manera más general, algunas autoridades de C ++, por ejemplo, Scott Meyers, sugieren
nunca llamar a funciones virtuales (incluso no puras) de constructores y constructores.
class transaction
{
public:
transaction(){ log_it(); }
virtual void log_it() const = 0;
};
https://riptutorial.com/es/home 170
};
sell_transaction s;
class base { };
class derived: public base { };
int main() {
base* p = new derived();
delete p; // The is undefined behavior!
}
En la sección [expr.delete] §5.3.5 / 3, el estándar dice que si se llama a delete en un objeto cuyo
tipo estático no tiene un destructor virtual :
Es ilegal acceder a una referencia a un objeto que ha quedado fuera del alcance o ha sido
destruido. Se dice que dicha referencia está colgando ya que ya no se refiere a un objeto válido.
#include <iostream>
int& getX() {
int x = 42;
return x;
}
int main() {
int& r = getX();
https://riptutorial.com/es/home 171
std::cout << r << "\n";
}
En este ejemplo, la variable local x queda fuera del alcance cuando retorna getX . (Tenga en
cuenta que la extensión de vida útil no puede extender la vida útil de una variable local más allá
del alcance del bloque en el que está definida). Por lo tanto, r es una referencia pendiente. Este
programa tiene un comportamiento indefinido, aunque puede parecer que funciona e imprime 42
en algunos casos.
Considera lo siguiente:
#include <algorithm>
namespace std
{
int foo(){}
}
Nada en el estándar prohíbe el algorithm (o uno de los encabezados que incluye) la definición de
la misma definición, por lo que este código violaría la Regla de una definición .
Entonces, en general, esto está prohibido. Sin embargo, hay excepciones específicas permitidas .
Tal vez lo más útil es que está permitido agregar especializaciones para los tipos definidos por el
usuario. Entonces, por ejemplo, supongamos que su código tiene
class foo
{
// Stuff
};
namespace std
{
template<>
struct hash<foo>
{
https://riptutorial.com/es/home 172
public:
size_t operator()(const foo &f) const;
};
}
El valor de origen está fuera del rango de valores que se pueden representar en el tipo de
destino, el resultado es un comportamiento indefinido. Ejemplo:
double x = 1e100;
int y = x; // int probably cannot hold numbers that large, so this is UB
Si se utiliza static_cast para convertir un puntero (referencia de referencia) en una clase base en
un puntero (referencia de referencia) en una clase derivada, pero el operando no apunta (referido
de referencia) a un objeto del tipo de clase derivada, el comportamiento es indefinido. Ver Base a
conversión derivada .
Para llamar a una función mediante un puntero de función, el tipo de puntero de función debe
coincidir exactamente con el tipo de función. De lo contrario, el comportamiento es indefinido.
Ejemplo:
int f();
void (*p)() = reinterpret_cast<void(*)()>(f);
p(); // undefined
https://riptutorial.com/es/home 173
Un compilador usualmente alineará el valor de un objeto const int , por lo que es posible que este
código compile e imprima 123 . Los compiladores también pueden colocar valores const objetos en
la memoria de solo lectura, por lo que puede ocurrir una falla de segmentación. En cualquier
caso, el comportamiento no está definido y el programa puede hacer cualquier cosa.
#include <iostream>
class Foo {
public:
int get_x() const { return m_x; }
void set_x(int x) { m_x = x; }
private:
Foo(int x, Foo*& this_ref): m_x(x) {
this_ref = this;
}
int m_x;
friend const Foo& getFoo();
};
void do_evil(int x) {
instance->set_x(x);
}
int main() {
const Foo& foo = getFoo();
do_evil(456);
std::cout << foo.get_x() << '\n';
}
En este código, getFoo crea un singleton de tipo const Foo y su miembro m_x se inicializa en 123 .
Luego se llama do_evil y el valor de foo.m_x aparentemente se cambia a 456. ¿Qué salió mal?
A pesar de su nombre, do_evil no hace nada particularmente malo; todo lo que hace es llamar a
un setter a través de un Foo* . Pero ese puntero apunta a un objeto const Foo aunque no se usó
const_cast . Este puntero se obtuvo a través del constructor de Foo . Un objeto const no se
convierte en const hasta que se completa su inicialización, por this tiene el tipo Foo* , no const
Foo* , dentro del constructor.
https://riptutorial.com/es/home 174
definido. (Este puntero a miembro se puede obtener a través de static_cast ).
Cuando static_cast se usa para convertir TD::* a TB::* , el miembro apuntado debe pertenecer a
una clase que sea una clase base o una clase derivada de B De lo contrario el comportamiento es
indefinido. Consulte Conversión derivada a base para punteros a miembros.
int a[10];
int* p1 = &a[5];
int* p2 = p1 + 4; // ok; p2 points to a[9]
int* p3 = p1 + 5; // ok; p2 points to one past the end of a
int* p4 = p1 + 6; // UB
int* p5 = p1 - 5; // ok; p2 points to a[0]
int* p6 = p1 - 6; // UB
int* p7 = p3 - 5; // ok; p7 points to a[5]
• Resta dos punteros si ambos no pertenecen al mismo objeto de matriz. (De nuevo, se
considera que el elemento uno más allá del final pertenece a la matriz.) La excepción es que
se pueden restar dos punteros nulos, lo que arroja 0.
int a[10];
int b[10];
int *p1 = &a[8], *p2 = &a[3];
int d1 = p1 - p2; // yields 5
int *p3 = p1 + 2; // ok; p3 points to one past the end of a
int d2 = p3 - p2; // yields 7
int *p4 = &b[0];
int d3 = p4 - p1; // UB
• Cualquier aritmética de punteros en la que el tipo de punto del operando no coincida con el
tipo dinámico del objeto apuntado (ignorando la calificación cv). De acuerdo con el estándar,
https://riptutorial.com/es/home 175
"[en] en particular, un puntero a una clase base no se puede usar para la aritmética de
punteros cuando la matriz contiene objetos de un tipo de clase derivada".
C ++ 11
En este ejemplo, se invoca explícitamente un destructor para un objeto que luego se destruirá
automáticamente.
struct S {
~S() { std::cout << "destroying S\n"; }
};
int main() {
https://riptutorial.com/es/home 176
S s;
s.~S();
} // UB: s destroyed a second time here
Otra forma de destruir un objeto dos veces es tener dos shared_ptr gestionen el objeto sin
compartir la propiedad entre ellos.
https://riptutorial.com/es/home 177
Capítulo 23: Comportamiento no
especificado
Observaciones
Si el comportamiento de una construcción no se especifica, entonces el estándar establece
algunas restricciones en el comportamiento, pero deja cierta libertad a la implementación, que no
es necesaria para documentar lo que sucede en una situación determinada. Contrasta con el
comportamiento definido por la implementación, en el que se requiere que la implementación
documente lo que sucede, y el comportamiento indefinido, en el que puede ocurrir cualquier cosa.
Examples
Orden de inicialización de globales a través de TU
Mientras que dentro de una unidad de traducción, se especifica el orden de inicialización de las
variables globales, el orden de inicialización entre unidades de traducción no está especificado.
• foo.cpp
#include <iostream>
• bar.cpp
#include <iostream>
• main.cpp
int main() {}
foobar
barfoo
https://riptutorial.com/es/home 178
Valor de una enumeración fuera de rango
Si una enumeración de ámbito se convierte en un tipo integral que es demasiado pequeño para
mantener su valor, el valor resultante no se especifica. Ejemplo:
enum class E {
X = 1,
Y = 1000,
};
// assume 1000 does not fit into a char
char c1 = static_cast<char>(E::X); // c1 is 1
char c2 = static_cast<char>(E::Y); // c2 has an unspecified value
Además, si un entero se convierte en una enumeración y el valor del entero está fuera del rango
de los valores de la enumeración, el valor resultante no se especifica. Ejemplo:
enum Color {
RED = 1,
GREEN = 2,
BLUE = 3,
};
Color c = static_cast<Color>(4);
Sin embargo, en el siguiente ejemplo, el comportamiento no está sin especificar, ya que el valor
de origen está dentro del rango de la enumeración, aunque es desigual para todos los
enumeradores:
enum Scale {
ONE = 1,
TWO = 2,
FOUR = 4,
};
Scale s = static_cast<Scale>(3);
El valor de p3 no está especificado porque p2 no puede apuntar a un objeto de tipo int ; su valor
no es una dirección correctamente alineada.
https://riptutorial.com/es/home 179
Resultado de algunas conversiones reinterpret_cast
int f();
auto fp = reinterpret_cast<int(*)(int)>(&f); // fp has unspecified value
C ++ 03
int x = 42;
char* p = reinterpret_cast<char*>(&x); // p has unspecified value
Si se comparan dos punteros utilizando < , > , <= o >= , el resultado no se especifica en los
siguientes casos:
• Los punteros apuntan a diferentes matrices. (Un objeto sin matriz se considera una matriz
de tamaño 1).
int x;
int y;
const bool b1 = &x < &y; // unspecified
int a[10];
const bool b2 = &a[0] < &a[1]; // true
const bool b3 = &a[0] < &x; // unspecified
const bool b4 = (a + 9) < (a + 10); // true
// note: a+10 points past the end of the array
• Los punteros apuntan al mismo objeto, pero a los miembros con un control de acceso
diferente.
class A {
public:
int x;
int y;
bool f1() { return &x < &y; } // true; x comes before y
bool f2() { return &x < &z; } // unspecified
private:
int z;
};
https://riptutorial.com/es/home 180
Espacio ocupado por una referencia.
• Si se aplica sizeof a una referencia, devuelve el tamaño del tipo referenciado, por lo que no
proporciona información sobre si la referencia ocupa algún almacenamiento.
• Las matrices de referencias son ilegales, por lo que no es posible examinar las direcciones
de dos elementos consecutivos de una referencia hipotética de matrices para determinar el
tamaño de una referencia.
• Si se toma la dirección de una referencia, el resultado es la dirección del referente, por lo
que no podemos obtener un puntero a la referencia en sí.
• Si una clase tiene un miembro de referencia, el intento de extraer la dirección de ese
miembro usando offsetof produce un comportamiento indefinido ya que dicha clase no es
una clase de diseño estándar.
• Si una clase tiene un miembro de referencia, la clase ya no es un diseño estándar, por lo
tanto, los intentos de acceder a los datos utilizados para almacenar los resultados de
referencia en un comportamiento no definido o no especificado.
void f() {
int x;
int& r = x;
// do something with r
}
el compilador es libre de simplemente tratar r como un alias para x y reemplazar todas las
apariciones de r en el resto de la función f con x , y no asignar ningún almacenamiento para
mantener r .
Si una función tiene múltiples argumentos, no se especifica en qué orden se evalúan. El siguiente
código podría imprimir x = 1, y = 2 o x = 2, y = 1 pero no se especifica cuál.
https://riptutorial.com/es/home 181
}
C ++ 17
Sin embargo, cada argumento de función se evalúa por completo, y se garantiza la evaluación del
objeto llamante antes de que lo sean los argumentos de función.
struct from_int {
from_int(int x) { std::cout << "from_int (" << x << ")\n"; }
};
int make_int(int x){ std::cout << "make_int (" << x << ")\n"; return x; }
bar
make_int(1)
from_int(1)
make_int(2)
from_int(2)
bar
make_int(2)
from_int(2)
make_int(1)
from_int(1)
que no se imprima bar después de cualquiera de la make o from 's, y que no se imprima:
bar
make_int(2)
make_int(1)
from_int(2)
from_int(1)
o similar. Antes de C ++ 17, la bar impresión después de make_int s era legal, al igual que hacer
https://riptutorial.com/es/home 182
ambos make_int s antes de hacer cualquier from_int s.
C ++ 11
Todos los contenedores de biblioteca estándar se dejan en un estado válido pero no especificado
después de ser movidos. Por ejemplo, en el siguiente código, v2 contendrá {1, 2, 3, 4} después
del movimiento, pero no se garantiza que v1 esté vacío.
int main() {
std::vector<int> v1{1, 2, 3, 4};
std::vector<int> v2 = std::move(v1);
}
Algunas clases tienen un estado movido desde exactamente definido. El caso más importante es
el de std::unique_ptr<T> , que se garantiza que será nulo después de ser movido.
https://riptutorial.com/es/home 183
Capítulo 24: Concurrencia con OpenMP
Introducción
Este tema cubre los conceptos básicos de la concurrencia en C ++ utilizando OpenMP. OpenMP
se documenta con más detalle en la etiqueta OpenMP .
Observaciones
OpenMP no requiere encabezados ni bibliotecas especiales, ya que es una característica de
compilador integrada. Sin embargo, si usa alguna de las funciones de la API de OpenMP como
omp_get_thread_num() , deberá incluir omp.h su biblioteca.
Las instrucciones pragma OpenMP se ignoran cuando la opción OpenMP no está habilitada
durante la compilación. Es posible que desee consultar la opción del compilador en el manual de
su compilador.
Examples
OpenMP: Secciones paralelas
Este ejemplo ilustra los conceptos básicos de la ejecución de secciones de código en paralelo.
Código de muestra
https://riptutorial.com/es/home 184
{
std::cout << "hello " << std::endl;
/** Do something **/
}
#pragma omp section
{
std::cout << "world " << std::endl;
/** Do something **/
}
}
// This line will not be executed until all the
// sections defined above terminates
std::cout << "end" << std::endl;
Salidas
Este ejemplo produce 2 salidas posibles y depende del sistema operativo y el hardware. La salida
también ilustra un problema de condición de carrera que se produciría a partir de dicha
implementación.
SALIDA A SALIDA B
Salida
https://riptutorial.com/es/home 185
• comienza el mundo hola para siempre termina
• comience hola para siempre fin del mundo
• comienza por siempre hola fin del mundo
Como el orden de ejecución no está garantizado, puede observar cualquiera de los resultados
anteriores.
Este ejemplo muestra cómo dividir un bucle en partes iguales y ejecutarlas en paralelo.
// Continue process
// Only when all threads completed their allocated
// loop job
...
* Tenga mucho cuidado de no modificar el tamaño del vector utilizado en paralelo para los bucles,
ya que los índices de rango asignado no se actualizan automáticamente .
Este ejemplo ilustra un concepto para realizar una reducción o recopilación utilizando std::vector
y OpenMP.
Suponemos que tenemos un escenario en el que queremos que varios subprocesos nos ayuden
a generar un montón de cosas, int se usa aquí para simplificar y se puede reemplazar con otros
tipos de datos.
Esto es particularmente útil cuando necesita combinar resultados de esclavos para evitar fallas de
segmentos o violaciones de acceso a la memoria y no desea usar bibliotecas o bibliotecas
personalizadas de contenedores de sincronización.
https://riptutorial.com/es/home 186
// slave thread, in this case a vector to hold each of their results
// We don't have to worry about how many threads were spawn or if we need
// to repeat this declaration or not.
std::vector<int> Slave;
// Tell the compiler to use all threads allocated for this parallel region
// to perform this loop in parts. Actual load appx = 1000000 / Thread Qty
// The nowait keyword tells the compiler that the slave threads don't
// have to wait for all other slaves to finish this for loop job
#pragma omp for nowait
for (size_t i = 0; i < 1000000; ++i
{
/* Do something */
....
Slave.push_back(...);
}
https://riptutorial.com/es/home 187
Capítulo 25: Const Correccion
Sintaxis
• class ClassOne {public: bool non_modifying_member_function () const {/ * ... * /}};
• int ClassTwo :: non_modifying_member_function () const {/ * ... * /}
• void ClassTwo :: modifying_member_function () {/ * ... * /}
• char non_param_modding_func (const ClassOne & one, const ClassTwo * two) {/ * ... * /}
• float parameters_modifying_function (ClassTwo & one, ClassOne * two) {/ * ... * /}
• short ClassThree :: non_modding_non_param_modding_f (const ClassOne &) const {/ * ... *
/}
Observaciones
const corrección de const es una herramienta de solución de problemas muy útil, ya que le permite
al programador determinar rápidamente qué funciones podrían estar modificando
inadvertidamente el código. También evita que los errores involuntarios, como el que se muestra
en Const Correct Function Parameters , se compilen correctamente y pasen desapercibidos.
Es mucho más fácil diseñar una clase para la corrección const , que luego agregar la corrección
const a una clase preexistente. Si es posible, diseñar cualquier clase que puede ser const correcta
para que sea const correcta, para salvar a sí mismo ya otros la molestia de tarde modificándolo.
Tenga en cuenta que esto también puede aplicarse a la corrección volatile si es necesario, con
las mismas reglas que para la corrección const , pero esto se usa con mucha menos frecuencia.
Refrences:
ISO_CPP
Tutorial de C ++
Examples
Los basicos
const corrección const es la práctica de diseñar código de modo que solo el código que necesita
modificar una instancia pueda modificar una instancia (es decir, tenga acceso de escritura) y, a la
inversa, que cualquier código que no necesite modificar una instancia no pueda hacerlo entonces
(es decir, solo tiene acceso de lectura). Esto evita que la instancia se modifique
involuntariamente, lo que hace que el código sea menos propenso a errores, y documenta si el
código está destinado a cambiar el estado de la instancia o no. También permite que las
instancias se traten como const siempre que no necesiten ser modificadas, o definidas como const
https://riptutorial.com/es/home 188
si no es necesario cambiarlas después de la inicialización, sin perder ninguna funcionalidad.
Esto se hace dando a los miembros las funciones const CV-qualifiers , y haciendo const
parámetros de puntero / referencia, excepto en el caso de que necesiten acceso de escritura.
class ConstCorrectClass {
int x;
public:
int getX() const { return x; } // Function is const: Doesn't modify instance.
void setX(int i) { x = i; } // Not const: Modifies instance.
};
// ...
Debido a la naturaleza de la corrección de const, esto comienza con las funciones de los
miembros de la clase y se abre camino hacia afuera; Si intenta llamar a una función miembro no
const desde una instancia const o desde una instancia no const tratada como const , el compilador
le dará un error sobre la pérdida de calificadores cv.
En una clase const , todas las funciones miembro que no cambian el estado lógico tienen this cv
calificado como const , lo que indica que no modifican el objeto (aparte de mutable campos mutable
, que se pueden modificar libremente incluso en casos const ); si una función calificada para cv
const devuelve una referencia, esa referencia también debe ser una const . Esto permite que se
los llame en instancias constantes y no calificadas para CV, ya que una const T* es capaz de
vincularse a una T* o una const T* . Esto, a su vez, permite que las funciones declaren sus
parámetros pasados por referencia como const cuando no necesitan ser modificados, sin perder
ninguna funcionalidad.
Además, en una clase correcta const , todos los parámetros de función pasados por referencia
serán const correctos, como se describe en Const Correct Function Parameters , de modo que solo
pueden modificarse cuando la función necesita modificarlos explícitamente.
https://riptutorial.com/es/home 189
Primero, echemos un vistazo a this calificadores cv:
class ConstIncorrect {
Field fld;
public:
ConstIncorrect(Field& f); // Modifies.
class ConstCorrectCVQ {
Field fld;
public:
ConstCorrectCVQ(Field& f); // Modifies.
const Field& getField() const; // Doesn't modify. Exposes member as const reference,
// preventing indirect modification.
void setField(Field& f); // Modifies.
ConstCorrectCVQ::ConstCorrectCVQ(Field& f) : fld(f) {}
Field& ConstCorrectCVQ::getField() const { return fld; }
void ConstCorrectCVQ::setField(Field& f) { fld = f; }
void ConstCorrectCVQ::doSomething(int i) {
fld.insert_value(i);
}
void ConstCorrectCVQ::doNothing() const {}
https://riptutorial.com/es/home 190
}
Entonces podemos combinar esto con Const Correct Function Parameters , causando la clase sea
plenamente const -correct.
class ConstCorrect {
Field fld;
public:
ConstCorrect(const Field& f); // Modifies instance. Doesn't modify parameter.
const Field& getField() const; // Doesn't modify. Exposes member as const reference,
// preventing indirect modification.
void setField(const Field& f); // Modifies instance. Doesn't modify parameter.
Esto también se puede combinar con la sobrecarga basada en const , en el caso de que
queramos un comportamiento si la instancia es const , y un comportamiento diferente si no lo es;
un uso común para esto es constainers que proporcionan accesores que solo permiten
modificaciones si el contenedor en sí no es const .
class ConstCorrectContainer {
int arr[5];
public:
// Subscript operator provides read access if instance is const, or read/write access
// otherwise.
int& operator[](size_t index) { return arr[index]; }
const int& operator[](size_t index) const { return arr[index]; }
// ...
};
En una función de corrección const , todos los parámetros pasados por referencia se marcan
como const menos que la función los modifique directa o indirectamente, lo que evita que el
programador cambie inadvertidamente algo que no pretendían cambiar. Esto permite que la
función de tomar tanto const y los casos no-CV-cualificado, ya su vez, hace que la instancia está
https://riptutorial.com/es/home 191
this a ser de tipo const T* cuando un miembro de la función se llama, donde T es el tipo de clase.
struct Example {
void func() { std::cout << 3 << std::endl; }
void func() const { std::cout << 5 << std::endl; }
};
int main() {
Example a, b;
const_incorrect_function(a, &b);
const_correct_function(a, &b);
}
// Output:
3
3
5
5
Si bien los efectos de esto son menos evidentes que los de const diseño de la clase correcta (en
ese const funciones -correct y const clases -incorrect causará errores de compilación, mientras
que const clases -correct y const funciones -incorrect compilará correctamente), const correcta las
funciones detectarán una gran cantidad de errores que las funciones const incorrectas dejarán
pasar, como la que se muestra a continuación. [Nota, sin embargo, que un const función -incorrect
causará errores de compilación si se aprueba una const ejemplo, cuando se espera que un no
const uno.]
if (vals.size() && (vals_ind != -1) && (vals_ind < vals.size()) && !(h.needs_recalc())) {
return vals[h.get_cache_index(v_ind)];
}
T temp = v[v_ind];
temp -= h.poll_device();
temp *= h.obtain_random();
temp += h.do_tedious_calculation(temp, v[h.get_last_handled_index()]);
https://riptutorial.com/es/home 192
// We're feeling tired all of a sudden, and this happens.
if (vals_ind != -1) {
vals[vals_ind] = temp;
} else {
v.push_back(temp); // Oops. Should've been accessing vals.
vals_ind = vals.size() - 1;
h.register_index(v_ind, vals_ind);
}
return vals[vals_ind];
}
return vals[vals_ind];
}
Una de las cosas más útiles acerca de la corrección const es que sirve como una forma de
documentar el código, proporcionando ciertas garantías al programador y otros usuarios. Estas
garantías son impuestas por el compilador debido a la const , con una falta de const a su vez
indica que el código no las proporciona.
Esto se puede usar para hacer suposiciones sobre el estado del objeto después de que se llame
a cualquier función miembro dada, incluso sin ver la definición de esa función:
https://riptutorial.com/es/home 193
// ConstMemberFunctions.h
class ConstMemberFunctions {
int val;
mutable int cache;
mutable bool state_changed;
public:
// Constructor clearly changes logical state. No assumptions necessary.
ConstMemberFunctions(int v = 0);
// We can assume this function doesn't change logical state, and doesn't call
// set_val(). It may or may not call squared_calc() or bad_func().
int calc() const;
// We can assume this function doesn't change logical state, and doesn't call
// set_val(). It may or may not call calc() or bad_func().
int squared_calc() const;
// We can assume this function doesn't change logical state, and doesn't call
// set_val(). It may or may not call calc() or squared_calc().
void bad_func() const;
// We can assume this function changes logical state, and may or may not call
// calc(), squared_calc(), or bad_func().
void set_val(int v);
};
Debido a las reglas const , estos supuestos serán de hecho aplicados por el compilador.
// ConstMemberFunctions.cpp
ConstMemberFunctions::ConstMemberFunctions(int v /* = 0*/)
: cache(0), val(v), state_changed(true) {}
return cache;
}
https://riptutorial.com/es/home 194
state_changed = true;
}
}
Esto se puede usar para hacer suposiciones sobre el estado de los parámetros después de pasar
a cualquier función dada, incluso sin ver la definición de esa función.
// function_parameter.h
// We can assume that c isn't modified (and c.set_val() isn't called), and isn't passed
// to non_qualified_function_parameter(). If passed to one_const_one_not(), it is the first
// parameter.
void const_function_parameter(const ConstMemberFunctions& c);
// We can assume that c is modified and/or c.set_val() is called, and may or may not be passed
// to any of these functions. If passed to one_const_one_not, it may be either parameter.
void non_qualified_function_parameter(ConstMemberFunctions& c);
// We can assume that c isn't modified (and c.set_val() isn't called), and isn't passed
// to non_qualified_function_parameter(). If passed to one_const_one_not(), it is the first
// parameter.
void bad_parameter(const ConstMemberFunctions& c);
Debido a las reglas const , estos supuestos serán de hecho aplicados por el compilador.
// function_parameter.cpp
https://riptutorial.com/es/home 195
<< std::endl;
}
const_function_parameter(l);
const_function_parameter(r);
}
Si bien es posible eludir la corrección const y, por extensión, romper estas garantías, el
programador debe hacer esto intencionalmente (al igual que romper la encapsulación con
Machiavelli , arriba), y es probable que cause un comportamiento indefinido.
// ...
https://riptutorial.com/es/home 196
Sin embargo, debido a que esto requiera el programador de decir muy específicamente al
compilador que tienen la intención de ignorar const Ness, y ser consistentes entre los
compiladores, generalmente es seguro asumir que const código correcto se abstendrá de hacerlo
a menos que se especifique lo contrario.
https://riptutorial.com/es/home 197
Capítulo 26: constexpr
Introducción
constexpres una palabra clave que se puede usar para marcar el valor de una variable como una
expresión constante, una función como potencialmente utilizable en expresiones constantes, o
(desde C ++ 17) una declaración if que tiene solo una de sus ramas seleccionadas para compilar.
Observaciones
La palabra clave constexpr se agregó en C ++ 11, pero desde hace algunos años desde que se
publicó el estándar C ++ 11, no todos los compiladores principales lo admitieron. En el momento
en que se publicó el estándar C ++ 11. En el momento de la publicación de C ++ 14, todos los
compiladores principales admiten constexpr .
Examples
variables constexpr
Una variable declarada constexpr es implícitamente const y su valor puede usarse como una
expresión constante.
C ++ 11
int main()
{
constexpr int N = 10 + 2;
cout << N;
}
#define N 10 + 2
int main()
{
cout << N;
https://riptutorial.com/es/home 198
}
Producirá:
cout << 10 + 2;
que obviamente se convertirá en cout << 10 + 2; . Sin embargo, el compilador tendría que hacer
más trabajo. Además, crea un problema si no se utiliza correctamente.
cout << N * 2;
formas:
cout << 10 + 2 * 2; // 14
Una variable const es una variable que necesita memoria para su almacenamiento. Un constexpr
no lo hace. Un constexpr produce una constante de tiempo de compilación, que no se puede
cambiar. Puedes argumentar que la const puede también ser cambiada. Pero considere:
int main()
{
const int size1 = 10;
const int size2 = abs(10);
int arr_one[size1];
int arr_two[size2];
}
Con la mayoría de los compiladores, la segunda instrucción fallará (puede funcionar con GCC,
por ejemplo). El tamaño de cualquier matriz, como usted sabe, tiene que ser una expresión
constante (es decir, resultados en valor de tiempo de compilación). A la segunda variable size2 se
le asigna algún valor que se decide en tiempo de ejecución (aunque sabe que es 10 , para el
compilador no es tiempo de compilación).
Esto significa que una const puede o no ser una verdadera constante de compilación. No puede
garantizar ni hacer cumplir que un valor const particular es absolutamente tiempo de compilación.
Puedes usar #define pero tiene sus propios escollos.
C ++ 11
int main()
{
https://riptutorial.com/es/home 199
constexpr int size = 10;
int arr[size];
}
Una expresión constexpr debe evaluar un valor en tiempo de compilación. Por lo tanto, no puede
utilizar:
C ++ 11
C ++ 11
C ++ 11
funciones constexpr
Una función que se declara constexpr está implícitamente en línea y las llamadas a dicha función
potencialmente producen expresiones constantes. Por ejemplo, la siguiente función, si se llama
con argumentos de expresión constante, también produce una expresión constante:
C ++ 11
Por lo tanto, el resultado de la llamada a la función se puede usar como una matriz enlazada o un
argumento de plantilla, o para inicializar una variable constexpr :
C ++ 11
int main()
{
constexpr int S = Sum(10,20);
int Array[S];
https://riptutorial.com/es/home 200
int Array2[Sum(20,30)]; // 50 array size, compile time
}
Lo interesante de constexpr funciones constexpr es que también puede usarlo como funciones
comunes:
C ++ 11
int a = 20;
auto sum = Sum(a, abs(-20));
Sumno será una función constexpr ahora, se compilará como una función ordinaria, tomando
argumentos variables (no constantes) y devolviendo un valor no constante. No necesitas escribir
dos funciones.
También significa que si intenta asignar dicha llamada a una variable no constante, no se
compilará:
C ++ 11
int a = 20;
constexpr auto sum = Sum(a, abs(-20));
La razón es simple: a constexpr solo se le debe asignar una constante de tiempo de compilación.
Sin embargo, la llamada a la función anterior hace que Sum no sea constexpr (el valor R es no
const, pero el valor L se declara a sí mismo como constexpr ).
La función constexpr también debe devolver una constante de tiempo de compilación. Lo siguiente
no se compilará:
C ++ 11
Porque a1 es una variable que no es constexpr, y prohíbe que la función sea una verdadera
función constexpr . constexpr y asignarle a testamento tampoco funcionará, ya que aún no se
conoce el valor de a (parámetro entrante):
C ++ 11
https://riptutorial.com/es/home 201
{
constexpr int a1 = a; // ERROR
..
C ++ 11
Dado que abs(a) no es una expresión constante (incluso abs(10) no funcionará, ya que abs no
devuelve constexpr int !
C ++ 11
Creamos nuestra propia función Abs , que es un constexpr , y el cuerpo de Abs tampoco rompe
ninguna regla. Además, en el sitio de llamada (dentro de Sum ), la expresión se evalúa como
constexpr . Por lo tanto, la llamada a Sum(-10, 20) será una expresión constante en tiempo de
compilación que resultará en 30 .
Estática si declaración
C ++ 17
La if constexpr se puede usar para compilar condicionalmente el código. La condición debe ser
una expresión constante. La rama no seleccionada se descarta. No se crea una instancia de una
declaración descartada dentro de una plantilla. Por ejemplo:
Además, no es necesario definir las variables y funciones que solo se usan dentro de las
https://riptutorial.com/es/home 202
sentencias descartadas, y las sentencias de return descartadas no se utilizan para la deducción
del tipo de devolución de la función.
if constexpr(false) {
foobar; // error; foobar has not been declared
std::vector<int> v("hello, world"); // error; no matching constructor
}
https://riptutorial.com/es/home 203
Capítulo 27: Construir sistemas
Introducción
C ++, al igual que C, tiene un historial largo y variado con respecto a los flujos de trabajo de
compilación y los procesos de construcción. Hoy en día, C ++ tiene varios sistemas de
compilación populares que se utilizan para compilar programas, a veces para múltiples
plataformas dentro de un sistema de compilación. Aquí, algunos sistemas de construcción serán
revisados y analizados.
Observaciones
Actualmente, no existe un sistema de compilación universal o dominante para C ++ que sea
popular y multiplataforma. Sin embargo, existen varios sistemas de compilación principales que
se adjuntan a las plataformas / proyectos principales, el más notable es GNU Make con el sistema
operativo GNU / Linux y NMAKE con el sistema de proyectos Visual C ++ / Visual Studio.
Examples
Generando entorno de construcción con CMake
CMake genera entornos de compilación para casi cualquier compilador o IDE a partir de una sola
definición de proyecto. Los siguientes ejemplos demostrarán cómo agregar un archivo CMake al
código C ++ multiplataforma "Hello World" .
cmake_minimum_required(VERSION 2.4)
project(HelloWorld)
add_executable(HelloWorld main.cpp)
Este archivo le dice a CMake el nombre del proyecto, qué versión de archivo debe esperar e
instrucciones para generar un ejecutable llamado "HelloWorld" que requiere main.cpp .
https://riptutorial.com/es/home 204
comandos:
> cmake .
CMake también puede abstraer los comandos básicos del shell de la plataforma del ejemplo
anterior:
CMake incluye generadores para una serie de herramientas de construcción e IDE comunes.
Para generar makefiles para nmake Visual Studio :
Introducción
GNU Make (styled make ) es un programa dedicado a la automatización de ejecutar comandos de
shell. GNU Make es un programa específico que pertenece a la familia Make. Sigue siendo
popular entre los sistemas operativos similares a Unix y POSIX, incluidos los derivados del kernel
de Linux, Mac OS X y BSD.
GNU Make es especialmente notable por estar vinculado al Proyecto GNU, que está vinculado al
popular sistema operativo GNU / Linux. GNU Make también tiene versiones compatibles que se
ejecutan en varias versiones de Windows y Mac OS X. También es una versión muy estable con
un significado histórico que sigue siendo popular. Es por estas razones que GNU Make se enseña
a menudo junto con C y C ++.
https://riptutorial.com/es/home 205
Reglas básicas
Para compilar con make, cree un Makefile en el directorio de su proyecto. Su Makefile podría ser
tan simple como:
Makefile
# Then, we say that we want to compile with g++'s recommended warnings and some extra ones.
CXXFLAGS=-Wall -Wextra -pedantic
SRCS=main.cpp
# When you call `make` at the command line, this "target" is called.
# The $(EXE) at the right says that the `all` target depends on the `$(EXE)` target.
# $(EXE) expands to be the content of the EXE variable
# Note: Because this is the first target, it becomes the default target if `make` is called
without target
all: $(EXE)
NOTA: asegúrese de que las sangrías estén con una pestaña, no con cuatro
espacios. De lo contrario, obtendrá un error de Makefile:10: *** missing separator.
Stop.
$ cd ~/Path/to/project
$ make
$ ls
app main.cpp Makefile
$ ./app
Hello World!
https://riptutorial.com/es/home 206
$ make clean
$ ls
main.cpp Makefile
Construcciones incrementales
Cuando empiezas a tener más archivos, make se vuelve más útil. ¿Qué pasa si editas a.cpp pero
no b.cpp ? Recompilar b.cpp tomaría más tiempo.
.
+-- src
| +-- a.cpp
| +-- a.hpp
| +-- b.cpp
| +-- b.hpp
+-- Makefile
Makefile
CXX=g++
CXXFLAGS=-Wall -Wextra -pedantic
EXE=app
SRCS_GLOB=src/*.cpp
SRCS=$(wildcard $(SRCS_GLOB))
OBJS=$(SRCS:.cpp=.o)
all: $(EXE)
$(EXE): $(OBJS)
@$(CXX) -o $@ $(OBJS)
depend: .depend
.depend: $(SRCS)
@-rm -f ./.depend
@$(CXX) $(CXXFLAGS) -MM $^>>./.depend
clean:
-rm -f $(EXE)
-rm $(OBJS)
-rm *~
-rm .depend
include .depend
De nuevo mira las pestañas. Este nuevo Makefile garantiza que solo recompile archivos
modificados, minimizando el tiempo de compilación.
https://riptutorial.com/es/home 207
Documentación
Para obtener más información sobre make, consulte la documentación oficial de Free Software
Foundation , la documentación de stackoverflow y la elaborada respuesta de dmckee sobre
stackoverflow .
Puede crear el código C ++ multiplataforma "Hello World" , utilizando Scons , una herramienta de
construcción de software en lenguaje Python .
Primero, cree un archivo llamado SConstruct (tenga en cuenta que SCons buscará un archivo con
este nombre exacto de forma predeterminada). Por ahora, el archivo debe estar en un directorio a
lo largo de su hello.cpp . Escribe en el nuevo archivo la línea.
Program('hello.cpp')
$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o hello.o -c hello.cpp
g++ -o hello hello.o
scons: done building targets.
Las clases Environment y Glob le ayudarán a configurar aún más qué construir. Por ejemplo, el
archivo SConstruct
env=Environment(CPPPATH='/usr/include/boost/',
CPPDEFINES=[],
LIBS=[],
SCONS_CXX_STANDARD="c++11"
)
env.Program('hello', Glob('src/*.cpp'))
construye el ejecutable hello , usando todos los archivos cpp en src . Su CPPPATH es
/usr/include/boost y especifica el estándar C ++ 11.
Ninja
Introducción
https://riptutorial.com/es/home 208
El sistema de construcción Ninja se describe en el sitio web del proyecto como "un sistema de
construcción pequeña con un enfoque en la velocidad". Ninja está diseñado para generar sus
archivos mediante generadores de archivos de sistema de compilación, y adopta un enfoque de
bajo nivel para construir sistemas, en contraste con los administradores de sistemas de
compilación de nivel superior como CMake o Meson.
Ninja está escrito principalmente en C ++ y Python, y fue creado como una alternativa al sistema
de construcción SCons para el proyecto Chromium.
Introducción
NMAKE es una utilidad de línea de comandos desarrollada por Microsoft para ser utilizada
principalmente junto con Microsoft Visual Studio y / o las herramientas de línea de comandos de
Visual C ++.
Autotools (GNU)
Introducción
Los Autotools son un grupo de programas que crean un sistema de compilación GNU para un
paquete de software dado. Es un conjunto de herramientas que trabajan en conjunto para
producir varios recursos de compilación, como un Makefile (para usar con GNU Make). Por lo
tanto, Autotools puede considerarse un generador de sistema de construcción de facto.
• Autoconf
• Automake (no debe confundirse con make )
En general, Autotools está destinado a generar el script compatible con Unix y Makefile para
permitir que el siguiente comando genere (e instale) la mayoría de los paquetes (en el caso
simple):
Como tal, Autotools también tiene una relación con ciertos administradores de paquetes,
especialmente aquellos que están conectados a sistemas operativos que cumplen con los
Estándares POSIX.
https://riptutorial.com/es/home 209
Lea Construir sistemas en línea: https://riptutorial.com/es/cplusplus/topic/8200/construir-sistemas
https://riptutorial.com/es/home 210
Capítulo 28: Contenedores C ++
Introducción
Los contenedores C ++ almacenan una colección de elementos. Los contenedores incluyen
vectores, listas, mapas, etc. Usando plantillas, los contenedores C ++ contienen colecciones de
primitivas (por ejemplo, ints) o clases personalizadas (por ejemplo, MyClass).
Examples
Diagrama de flujo de contenedores C ++
Elegir qué contenedor de C ++ usar puede ser complicado, así que aquí hay un diagrama de flujo
simple para ayudarlo a decidir qué contenedor es el adecuado para el trabajo.
https://riptutorial.com/es/home 211
https://riptutorial.com/es/home 212
. Este pequeño gráfico en el diagrama de flujo es de Megan Hopkins
https://riptutorial.com/es/home 213
Capítulo 29: Control de flujo
Observaciones
Consulte el tema de los bucles para los diferentes tipos de bucles.
Examples
caso
Introduce una etiqueta de caso de una declaración de cambio. El operando debe ser una
expresión constante y coincidir con la condición de cambio en el tipo. Cuando se ejecuta la
instrucción de cambio, saltará a la etiqueta del caso con el operando igual a la condición, si la
hay.
char c = getchar();
bool confirmed;
switch (c) {
case 'y':
confirmed = true;
break;
case 'n':
confirmed = false;
break;
default:
std::cout << "invalid response!\n";
abort();
}
cambiar
El switch palabra clave va seguido de una condición entre paréntesis y un bloque, que puede
contener etiquetas de case y una etiqueta default opcional. Cuando se ejecuta la instrucción de
cambio, el control se transferirá a una etiqueta de case con un valor que coincida con el de la
condición, si corresponde, o a la etiqueta default , si corresponde.
La condición debe ser una expresión o una declaración, que tenga un tipo de entero o
enumeración, o un tipo de clase con una función de conversión a tipo de entero o enumeración.
char c = getchar();
bool confirmed;
switch (c) {
case 'y':
confirmed = true;
https://riptutorial.com/es/home 214
break;
case 'n':
confirmed = false;
break;
default:
std::cout << "invalid response!\n";
abort();
}
captura
try {
std::vector<int> v(N);
// do something
} catch (const std::bad_alloc&) {
std::cout << "failed to allocate memory for vector!" << std::endl;
} catch (const std::runtime_error& e) {
std::cout << "runtime error: " << e.what() << std::endl;
} catch (...) {
std::cout << "unexpected exception!" << std::endl;
throw;
}
defecto
char c = getchar();
bool confirmed;
switch (c) {
case 'y':
confirmed = true;
break;
case 'n':
confirmed = false;
break;
default:
std::cout << "invalid response!\n";
abort();
}
C ++ 11
https://riptutorial.com/es/home 215
destructor, un operador de asignación de copia o un operador de asignación de movimiento para
tener su comportamiento predeterminado.
class Base {
// ...
// we want to be able to delete derived classes through Base*,
// but have the usual behaviour for Base's destructor.
virtual ~Base() = default;
};
Si
Introduce una sentencia if. La palabra clave if debe ir seguida de una condición entre paréntesis,
que puede ser una expresión o una declaración. Si la condición es verdadera, se ejecutará la
subestación posterior a la condición.
int x;
std::cout << "Please enter a positive number." << std::endl;
std::cin >> x;
if (x <= 0) {
std::cout << "You didn't enter a positive number!" << std::endl;
abort();
}
más
int x;
std::cin >> x;
if (x%2 == 0) {
std::cout << "The number is even\n";
} else {
std::cout << "The number is odd\n";
}
ir
Salta a una declaración etiquetada, que debe estar ubicada en la función actual.
https://riptutorial.com/es/home 216
release_widget(widget);
return result;
}
regreso
int f() {
return 42;
}
int x = f(); // x is 42
int g() {
return 3.14;
}
int y = g(); // y is 3
Si return no tiene un operando, la función debe tener un tipo de retorno void . Como caso
especial, una función void -returning también puede devolver una expresión si la expresión tiene
el tipo void .
void f(int x) {
if (x < 0) return;
std::cout << sqrt(x);
}
int g() { return 42; }
void h() {
return f(); // calls f, then returns
return g(); // ill-formed
}
Cuando se devuelve main , std::exit se llama implícitamente con el valor de retorno, y el valor se
devuelve al entorno de ejecución. (Sin embargo, regresar de main destruye las variables locales
automáticas, mientras que llamar std::exit directamente no lo hace).
lanzar
1. Cuando se produce el throw en una expresión con un operando, su efecto es lanzar una
excepción , que es una copia del operando.
https://riptutorial.com/es/home 217
throw std::invalid_argument("count cannot be negative!");
}
while (count--) { putchar('*'); }
}
2. Cuando se produce el throw en una expresión sin un operando, su efecto es volver a emitir
la excepción actual . Si no hay una excepción actual, se llama a std::terminate .
try {
// something risky
} catch (const std::bad_alloc&) {
std::cerr << "out of memory" << std::endl;
} catch (...) {
std::cerr << "unexpected exception" << std::endl;
// hope the caller knows how to handle this exception
throw;
}
Tenga en cuenta que los dos primeros usos de throw enumerados anteriormente constituyen
expresiones en lugar de declaraciones. (El tipo de una expresión de lanzamiento es void ). Esto
hace posible anidarlos dentro de expresiones, de esta manera:
tratar
A la palabra clave try le sigue un bloque, o una lista de inicialización de constructor y luego un
bloque (ver aquí ). Al bloque try le sigue uno o más bloques catch . Si una excepción se propaga
fuera del bloque try, cada uno de los bloques catch correspondientes después del bloque try tiene
la oportunidad de manejar la excepción, si los tipos coinciden.
https://riptutorial.com/es/home 218
}
si y si no
se usaba para verificar si la expresión dada devuelve verdadero o falso y actúa como tal:
if (condition) statement
la condición puede ser cualquier expresión válida de C ++ que devuelva algo que se verifique con
la verdad / falsedad, por ejemplo:
if (true) { /* code here */ } // evaluate that true is true and execute the code in the
brackets
if (false) { /* code here */ } // always skip the code since false is always false
La condición puede ser cualquier cosa, una función, una variable o una comparación, por
ejemplo.
if(istrue()) { } // evaluate the function, if it returns true, the if will execute the code
if(isTrue(var)) { } //evalute the return of the function after passing the argument var
if(a == b) { } // this will evaluate the return of the experssion (a==b) which will be true if
equal and false if unequal
if(a) { } //if a is a boolean type, it will evaluate for its value, if it's an integer, any
non zero value will be true,
if (a && b) { } // will be true only if both a and b are true (binary operators are outside
the scope here
if (a || b ) { } //true if a or b is true
if (a== "test") {
//will execute if a is a string "test"
} else {
// only if the first failed, will execute
}
if (a=='a') {
// if a is a char valued 'a'
} else if (a=='b') {
// if a is a char valued 'b'
https://riptutorial.com/es/home 219
} else if (a=='c') {
// if a is a char valued 'c'
} else {
//if a is none of the above
}
sin embargo, debe tenerse en cuenta que debe usar ' switch ' en su lugar si su código verifica el
valor de la misma variable
La instrucción de descanso:
Usando break podemos dejar un bucle incluso si la condición para su final no se cumple. Puede
usarse para terminar un bucle infinito, o para forzarlo a que termine antes de su final natural.
La sintaxis es
break;
Ejemplo : a menudo utilizamos break en los casos de switch , es decir, una vez que se cumple un
caso, el bloque de código de esa condición se ejecuta.
switch(conditon){
case 1: block1;
case 2: block2;
case 3: block3;
default: blockdefault;
}
switch(condition){
case 1: block1;
break;
case 2: block2;
break;
case 3: block3;
break;
default: blockdefault;
break;
}
por lo que solo se procesa un bloque y el control sale del bucle de conmutación.
break también se puede usar en otros bucles condicionales y no condicionales como if , while ,
for etc;
ejemplo:
https://riptutorial.com/es/home 220
if(condition1){
....
if(condition2){
.......
break;
}
...
}
La instrucción continua:
La instrucción de continuación hace que el programa omita el resto del bucle en la iteración actual
como si se hubiera llegado al final del bloque de instrucciones, lo que provocó que saltara a la
siguiente iteración.
La sintaxis es
continue;
for(int i=0;i<10;i++){
if(i%2==0)
continue;
cout<<"\n @"<<i;
}
@1
@3
@5
@7
@9
i este código siempre que se cumpla la condición i%2==0 continue se procesa, esto hace que el
compilador omita todo el código restante (imprimiendo @ ei) y se ejecute la declaración de
incremento / decremento del bucle.
La instrucción goto:
Permite realizar un salto absoluto a otro punto del programa. Debe usar esta función con cuidado,
https://riptutorial.com/es/home 221
ya que su ejecución ignora cualquier tipo de limitación de anidamiento. El punto de destino se
identifica mediante una etiqueta, que luego se utiliza como un argumento para la instrucción goto.
Una etiqueta está hecha de un identificador válido seguido de dos puntos (:)
La sintaxis es
goto label;
..
.
label: statement;
Ejemplo:
int num = 1;
STEP:
do{
if( num%2==0 )
{
num = num + 1;
goto STEP;
}
salida:
https://riptutorial.com/es/home 222
value of num : 1
value of num : 3
value of num : 5
value of num : 7
value of num : 9
siempre que se cumpla la condición num%2==0 goto envía el control de ejecución al comienzo del
bucle do-while while.
La función de salida:
exites una función definida en cstdlib . El propósito de exit es terminar el programa en ejecución
con un código de salida específico. Su prototipo es:
https://riptutorial.com/es/home 223
Capítulo 30: Conversiones de tipo explícito
Introducción
Una expresión puede ser convertido o fundido para escribir explícitamente T usando
dynamic_cast<T> , static_cast<T> , reinterpret_cast<T> , o const_cast<T> , dependiendo de qué tipo
de molde que se pretende.
Sintaxis
• especificador de tipo simple ( )
• especificador de tipo simple ( expresión-lista )
• especificador de tipo simple braced-init-list
• typename-specifier ( )
• typename-specifier ( expresión-lista )
• nombre de archivo-especificador braced- init-list
• dynamic_cast < type-id > ( expresión )
• static_cast < type-id > ( expresión )
• reinterpret_cast < type-id > ( expresión )
• const_cast < type-id > ( expresión )
• ( type-id ) expresión-cast
Observaciones
Las seis notaciones emitidas tienen una cosa en común:
• Las conversiones de "tipo punning" , que se pueden usar para acceder a la memoria de un
tipo como si fuera de un tipo diferente.
• Conversiones entre tipos enteros y tipos de punteros , en cualquier dirección.
https://riptutorial.com/es/home 224
La palabra clave static_cast puede realizar una variedad de diferentes conversiones:
• Cualquier conversión que se pueda realizar mediante una inicialización directa, incluidas las
conversiones implícitas y las conversiones que llaman a un constructor explícito o una
función de conversión. Vea aquí y aquí para más detalles.
• Entre los tipos aritméticos y de enumeración, y entre diferentes tipos de enumeración. Ver
las conversiones de enumeración
• Del puntero al miembro de la clase derivada, al puntero del miembro de la clase base. Los
tipos apuntados deben coincidir. Ver conversión derivada a base para punteros a miembros
• void* a T* .
C ++ 11
Examples
Base a conversión derivada
Del mismo modo, una referencia a la clase base se puede convertir en una referencia a la clase
derivada usando static_cast .
https://riptutorial.com/es/home 225
Derived& r2 = r1; // error; cast required
Derived& r3 = static_cast<Derived&>(r1); // OK; r3 now refers to Derived object
Si el tipo de fuente es polimórfico, dynamic_cast se puede usar para realizar una conversión de
base a derivada. Realiza una verificación en tiempo de ejecución y la falla es recuperable en lugar
de producir un comportamiento indefinido. En el caso del puntero, se devuelve un puntero nulo en
caso de error. En el caso de referencia, se produce una excepción en caso de error de tipo
std::bad_cast (o una clase derivada de std::bad_cast ).
Arrojando constness
void bad_strlen(char*);
const char* s = "hello, world!";
bad_strlen(s); // compile error
bad_strlen(const_cast<char*>(s)); // OK, but it's better to make bad_strlen accept const char*
const_castpuede utilizar const_cast al tipo de referencia para convertir un lvalue cualificado por
const en un valor no calificado por const.
const_cast es peligroso porque hace imposible que el sistema de tipo C ++ impida que intente
modificar un objeto const. Si lo hace, se traduce en un comportamiento indefinido.
int x = 42;
char* p = static_cast<char*>(&x); // error: static_cast cannot perform this conversion
char* p = reinterpret_cast<char*>(&x); // OK
*p = 'z'; // maybe this modifies x (see below)
C ++ 11
https://riptutorial.com/es/home 226
El resultado de reinterpret_cast representa la misma dirección que el operando, siempre que la
dirección esté alineada adecuadamente para el tipo de destino. De lo contrario, el resultado no se
especifica.
int x = 42;
char& r = reinterpret_cast<char&>(x);
const void* px = &x;
const void* pr = &r;
assert(px == pr); // should never fire
C ++ 11
int x = 123;
unsigned int& r1 = reinterpret_cast<unsigned int&>(x);
int& r2 = reinterpret_cast<int&>(r1);
r2 = 456; // sets x to 456
reinterpret_cast también se puede utilizar para convertir de un tipo de puntero a datos a otro, o
un tipo de función de puntero a miembro a otro.
Un puntero de objeto (incluido void* ) o un puntero de función se puede convertir a un tipo entero
usando reinterpret_cast . Esto solo se compilará si el tipo de destino es lo suficientemente largo.
El resultado está definido por la implementación y generalmente proporciona la dirección
numérica del byte en memoria a la que apuntan los punteros.
Por lo general, long o unsigned long es lo suficientemente largo para contener cualquier valor de
puntero, pero esto no está garantizado por la norma.
C ++ 11
Si los tipos std::intptr_t y std::uintptr_t existen, se garantiza que son lo suficientemente largos
para contener un void* (y, por lo tanto, cualquier puntero al tipo de objeto). Sin embargo, no se
garantiza que sean lo suficientemente largos para contener un puntero de función.
De manera similar, reinterpret_cast se puede usar para convertir un tipo entero en un tipo de
puntero. Nuevamente, el resultado está definido por la implementación, pero se garantiza que un
https://riptutorial.com/es/home 227
valor de puntero no se modificará en un viaje de ida y vuelta a través de un tipo entero. El
estándar no garantiza que el valor cero se convierta en un puntero nulo.
Una conversión que implique llamar a un constructor explícito o una función de conversión no se
puede hacer de manera implícita. Podemos solicitar que la conversión se realice explícitamente
usando static_cast . El significado es el mismo que el de una inicialización directa, excepto que el
resultado es temporal.
class C {
std::unique_ptr<int> p;
public:
explicit C(int* p) : p(p) {}
};
void f(C c);
void g(int* p) {
f(p); // error: C::C(int*) is explicit
f(static_cast<C>(p)); // ok
f(C(p)); // equivalent to previous line
C c(p); f(c); // error: C is not copyable
}
Conversión implícita
Sin la conversión explícita de tipos, se pasaría un objeto double a los puntos suspensivos y
se produciría un comportamiento indefinido.
https://riptutorial.com/es/home 228
struct Base { /* ... */ };
struct Derived : Base {
Derived& operator=(const Derived& other) {
static_cast<Base&>(*this) = other;
// alternative:
// Base& this_base_ref = *this; this_base_ref = other;
}
};
static_cast puede convertir de un tipo de punto flotante o entero a un tipo de enumeración (ya
sea con o sin ámbito), y viceversa. También puede convertir entre tipos de enumeración.
C ++ 11
Ejemplo:
Ejemplo:
enum Scale {
SINGLE = 1,
https://riptutorial.com/es/home 229
DOUBLE = 2,
QUAD = 4
};
Scale s1 = 1; // error
Scale s2 = static_cast<Scale>(2); // s2 is DOUBLE
Scale s3 = static_cast<Scale>(3); // s3 has value 3, and is not equal to any enumerator
Scale s9 = static_cast<Scale>(9); // unspecified value in C++14; UB in C++17
C ++ 11
enum Direction {
UP = 0,
LEFT = 1,
DOWN = 2,
RIGHT = 3,
};
Direction d = static_cast<Direction>(3.14); // d is RIGHT
struct A {};
struct B { int x; };
struct C : A, B { int y; double z; };
int B::*p1 = &B::x;
int C::*p2 = p1; // ok; implicit conversion
int B::*p3 = p2; // error
int B::*p4 = static_cast<int B::*>(p2); // ok; p4 is equal to p1
int A::*p5 = static_cast<int A::*>(p2); // undefined; p2 points to x, which is a member
// of the unrelated class B
double C::*p6 = &C::z;
double A::*p7 = static_cast<double A::*>(p6); // ok, even though A doesn't contain z
int A::*p8 = static_cast<int A::*>(p6); // error: types don't match
nulo * a T *
https://riptutorial.com/es/home 230
realmente apunta a un objeto T , el resultado apunta a ese objeto. De lo contrario, el resultado no
se especifica.
C ++ 11
Incluso si el operando no apunta a un objeto T , siempre que el operando apunte a un byte cuya
dirección esté correctamente alineada para el tipo T , el resultado de la conversión apunta al
mismo byte.
Casting estilo c
Cuando se utiliza este lanzamiento, usa uno de los siguientes lanzamientos de c ++ (en orden):
• const_cast<NewType>(variable)
• static_cast<NewType>(variable)
• const_cast<NewType>(static_cast<const NewType>(variable))
• reinterpret_cast<const NewType>(variable)
• const_cast<NewType>(reinterpret_cast<const NewType>(variable))
La conversión funcional es muy similar, aunque como pocas restricciones como resultado de su
sintaxis: NewType(expression) . Como resultado, solo se pueden convertir los tipos sin espacios.
Es mejor usar el nuevo c ++ cast, porque es más legible y se puede detectar fácilmente en
cualquier lugar dentro de un código fuente de C ++ y los errores se detectarán en tiempo de
compilación, en lugar de en tiempo de ejecución.
Como este lanzamiento puede dar lugar a reinterpret_cast no deseado, a menudo se considera
peligroso.
https://riptutorial.com/es/home 231
Capítulo 31: Copia elision
Examples
Propósito de la copia elision
Hay lugares en el estándar donde un objeto se copia o mueve para inicializar un objeto. Elección
de copia (a veces llamada optimización de valor de retorno) es una optimización por la cual, bajo
ciertas circunstancias específicas, se permite que un compilador evite la copia o el movimiento
aunque la norma diga que debe suceder.
std::string get_string()
{
return std::string("I am a string.");
}
De acuerdo con la estricta redacción de la norma, esta función inicializará una std::string
temporal, luego la copiará / moverá al objeto de valor de retorno y luego destruirá la temporal. El
estándar es muy claro que así se interpreta el código.
Copy elision es una regla que permite a un compilador de C ++ ignorar la creación del temporal y
su posterior copia / destrucción. Es decir, el compilador puede tomar la expresión de inicialización
para el temporal e inicializar directamente el valor de retorno de la función. Esto obviamente
guarda el rendimiento.
1. El tipo debe tener el constructor de copiar / mover que se habría llamado. Incluso si el
compilador evita la copia / movimiento, el tipo aún debe poder copiarse / moverse.
C ++ 11
struct my_type
{
my_type() = default;
my_type(const my_type &) {std::cout <<"Copying\n";}
my_type(my_type &&) {std::cout <<"Moving\n";}
};
my_type func()
{
return my_type();
}
https://riptutorial.com/es/home 232
¿Qué hará la func llamada? Bueno, nunca se imprimirá "Copiando", ya que el temporal es un
rvalue y my_type es un tipo movible. Entonces, ¿se imprimirá "Moving"?
Sin la regla de elision de copia, esto se requeriría para imprimir siempre "Mover". Pero como la
regla de elision de copia existe, el constructor de movimientos puede o no ser llamado; es
dependiente de la implementación
Debido a que elision es una optimización, su compilador puede no ser compatible con elision en
todos los casos. Y sin importar si el compilador elude un caso particular o no, el tipo todavía debe
soportar la operación que se está elidiando. Por lo tanto, si se elimina una construcción de copia,
el tipo aún debe tener un constructor de copia, aunque no se llamará.
C ++ 17
Normalmente, elision es una optimización. Si bien prácticamente todos los compiladores son
compatibles con la copia de la elision en los casos más simples, tenerla aún representa una carga
particular para los usuarios. A saber, el tipo de quién se está eliminando la copia / movimiento
debe seguir teniendo la operación de copia / movimiento que fue eliminada.
Por ejemplo:
std::mutex a_mutex;
std::lock_guard<std::mutex> get_lock()
{
return std::lock_guard<std::mutex>(a_mutex);
}
Esto puede ser útil en los casos en que a_mutex es un mutex que es privado en algún sistema,
pero un usuario externo puede querer tener un bloqueo de ámbito en él.
Esto tampoco es legal, porque std::lock_guard no se puede copiar o mover. A pesar de que
virtualmente cada compilador de C ++ ocultará la copia / movimiento, el estándar aún requiere
que el tipo tenga esa operación disponible.
Hasta C ++ 17.
Bajo la redacción anterior a C ++ 17, ese código dice crear un temporal y luego usar el temporal
para copiar / mover al valor de retorno, pero la copia temporal puede eliminarse. Bajo la redacción
de C ++ 17, eso no crea un temporal en absoluto.
En C ++ 17, cualquier expresión prvalue , cuando se usa para inicializar un objeto del mismo tipo
que la expresión, no genera un temporal. La expresión inicializa directamente ese objeto. Si
https://riptutorial.com/es/home 233
devuelve un prvalue del mismo tipo que el valor de retorno, entonces el tipo no necesita tener un
constructor de copiar / mover. Y por lo tanto, bajo las reglas de C ++ 17, el código anterior puede
funcionar.
La redacción de C ++ 17 funciona en los casos en que el tipo del prvalue coincide con el tipo que
se está inicializando. Por lo tanto, dado get_lock arriba, esto tampoco requerirá una copia /
movimiento:
Dado que el resultado de get_lock es una expresión prvalue que se utiliza para inicializar un
objeto del mismo tipo, no se realizará ninguna copia o movimiento. Esa expresión nunca crea un
temporal; se utiliza para inicializar directamente the_lock . No hay elision porque no hay copia /
movimiento para ser elide elide.
El término "elision de copia garantizada" es, por lo tanto, un nombre poco apropiado, pero ese es
el nombre de la función tal como se propone para la estandarización de C ++ 17 . No garantiza en
absoluto la elisión; elimina la copia / movimiento por completo, redefiniendo C ++ para que nunca
haya una copia / movimiento que deba ser borrado.
Esta característica solo funciona en casos que involucran una expresión prvalue. Como tal, esto
usa las reglas habituales de elision:
std::mutex a_mutex;
std::lock_guard<std::mutex> get_lock()
{
std::lock_guard<std::mutex> my_lock(a_mutex);
//Do stuff
return my_lock;
}
Si bien este es un caso válido para la elección de copia, las reglas de C ++ 17 no eliminan la
copia / movimiento en este caso. Como tal, el tipo aún debe tener un constructor de copiar /
mover para usar para inicializar el valor de retorno. Y como lock_guard no lo hace, esto sigue
siendo un error de compilación. Se permite que las implementaciones se nieguen a rechazar
copias al pasar o devolver un objeto de tipo de copia trivial. Esto es para permitir el
desplazamiento de tales objetos en registros, que algunas ABI podrían imponer en sus
convenciones de llamada.
struct trivially_copyable {
int a;
};
Si devuelve una expresión prvalue de una función, y la expresión prvalue tiene el mismo tipo que
https://riptutorial.com/es/home 234
el tipo de retorno de la función, entonces se puede eliminar la copia del prvalue temporal:
std::string func()
{
return std::string("foo");
}
Parámetro elision
Cuando pasa un argumento a una función, y el argumento es una expresión prvalue del tipo de
parámetro de la función, y este tipo no es una referencia, entonces la construcción del prvalue se
puede eliminar.
func(std::string("foo"));
Esto dice que para crear una string temporal, luego muévala en el parámetro de función str .
Copy elision permite que esta expresión cree directamente el objeto en str , en lugar de usar un
movimiento + temporal.
Esta es una optimización útil para los casos en que un constructor se declara explicit . Por
ejemplo, podríamos haber escrito lo anterior como func("foo") , pero solo porque la string tiene
un constructor implícito que convierte de const char* a una string . Si ese constructor fuera
explicit , nos veríamos obligados a usar un temporal para llamar al constructor explicit . Copiar
elision nos evita tener que hacer una copia / movimiento innecesario.
Si devuelve una expresión lvalue desde una función, y este valor lvalue:
• representa una variable automática local para esa función, que se destruirá después de la
return
• La variable automática no es un parámetro de función.
• y el tipo de la variable es el mismo tipo que el tipo de retorno de la función
Si todos estos son el caso, entonces la copia / movimiento desde el lvalue se puede eliminar:
std::string func()
{
std::string str("foo");
//Do stuff
return str;
}
Los casos más complejos son elegibles para la elección, pero cuanto más complejo sea el caso,
menos probable será que el compilador realmente lo evite:
https://riptutorial.com/es/home 235
std::string func()
{
std::string ret("foo");
if(some_condition)
{
return "bar";
}
return ret;
}
El compilador todavía podría elidir a ret , pero las posibilidades de que lo hagan disminuyen.
Como se señaló anteriormente, elision no está permitido para los parámetros de valor.
Si utiliza una expresión prvalue para copiar, inicialice una variable, y esa variable tiene el mismo
tipo que la expresión prvalue, entonces la copia puede ser eliminada.
std::string func()
{
return std::string("foo");
}
https://riptutorial.com/es/home 236
Capítulo 32: Copiando vs Asignación
Sintaxis
• Copia Constructor
• MyClass (const MyClass y otros);
• MyClass (MyClass y otros);
• MyClass (const. Volátil MyClass y otros);
• MyClass (MyClass volátil y otros);
• Constructor de Asignaciones
• MyClass & operator = (const MyClass & rhs);
• MyClass & operator = (MyClass & rhs);
• MyClass & operator = (MyClass rhs);
• const MyClass & operator = (const MyClass & rhs);
• const MyClass & operator = (MyClass & rhs);
• const MyClass & operator = (MyClass rhs);
• Operador MyClass = (const MyClass & rhs);
• Operador MyClass = (MyClass & rhs);
• Operador MyClass = (myClass rhs);
Parámetros
Marcador de
Marcador de posición
posición
Observaciones
Otros buenos recursos para futuras investigaciones:
GeeksForGeeks
Artículos de C ++
Examples
https://riptutorial.com/es/home 237
Operador de Asignación
// Assignment Operator
#include <iostream>
#include <string>
using std::cout;
using std::endl;
class Foo
{
public:
Foo(int data)
{
this->data = data;
}
~Foo(){};
Foo& operator=(const Foo& rhs)
{
data = rhs.data;
return *this;
}
int data;
};
int main()
{
Foo foo(2); //Foo(int data) called
Foo foo2(42);
foo = foo2; // Assignment Operator Called
cout << foo.data << endl; //Prints 42
}
Puede ver que aquí llamo al operador de asignación cuando ya inicialicé el objeto foo . Luego
más tarde le asigno foo2 a foo . Todos los cambios que aparecen cuando llama a ese operador de
signo igual se define en su función operator= . Puede ver un resultado ejecutable aquí:
http://cpp.sh/3qtbm
Copia Constructor
Copia del constructor, por otro lado, es el opuesto completo del Constructor de asignación. Esta
vez, se utiliza para inicializar un objeto ya inexistente (o no inicializado previamente). Esto
significa que copia todos los datos del objeto al que se lo está asignando, sin inicializar realmente
el objeto en el que se está copiando. Ahora echemos un vistazo al mismo código que antes, pero
modifiquemos el constructor de asignaciones para que sea un constructor de copia:
// Copy Constructor
#include <iostream>
#include <string>
using std::cout;
https://riptutorial.com/es/home 238
using std::endl;
class Foo
{
public:
Foo(int data)
{
this->data = data;
}
~Foo(){};
Foo(const Foo& rhs)
{
data = rhs.data;
}
int data;
};
int main()
{
Foo foo(2); //Foo(int data) called
Foo foo2 = foo; // Copy Constructor called
cout << foo2.data << endl;
}
Puedes ver aquí Foo foo2 = foo; en la función principal, asigno inmediatamente el objeto antes de
inicializarlo, lo cual, como se dijo antes, significa que es un constructor de copia. Y note que no
necesito pasar el parámetro int para el objeto foo2 ya que automáticamente extraje los datos
anteriores del objeto foo. Aquí hay un ejemplo de salida: http://cpp.sh/5iu7
Ok, hemos revisado brevemente lo que el constructor de copia y el de asignación están arriba y
damos ejemplos de cada uno ahora veamos a ambos en el mismo código. Este código será
similar al anterior. Tomemos esto:
using std::cout;
using std::endl;
class Foo
{
public:
Foo(int data)
{
this->data = data;
}
~Foo(){};
Foo(const Foo& rhs)
{
data = rhs.data;
}
https://riptutorial.com/es/home 239
{
data = rhs.data;
return *this;
}
int data;
};
int main()
{
Foo foo(2); //Foo(int data) / Normal Constructor called
Foo foo2 = foo; //Copy Constructor Called
cout << foo2.data << endl;
Foo foo3(42);
foo3=foo; //Assignment Constructor Called
cout << foo3.data << endl;
}
Salida:
2
2
Aquí puede ver que primero llamamos al constructor de copia ejecutando la línea Foo foo2 = foo; .
Ya que no lo inicializamos previamente. Y luego, a continuación, llamamos al operador de
asignación en foo3 ya que ya estaba inicializado foo3=foo ;
https://riptutorial.com/es/home 240
Capítulo 33: decltype
Introducción
La palabra clave decltype se puede usar para obtener el tipo de una variable, función o expresión.
Examples
Ejemplo básico
Este ejemplo solo ilustra cómo se puede utilizar esta palabra clave.
int a = 10;
float a=99.0f;
Otro ejemplo
std::vector<int> intVector;
Y queremos declarar un iterador para este vector. Una idea obvia es usar auto . Sin embargo,
puede ser necesario simplemente declarar una variable de iterador (y no asignarla a nada).
Haríamos:
vector<int>::iterator iter;
Sin embargo, con decltype se vuelve fácil y menos propenso a errores (si cambia el tipo de
intVector ).
decltype(intVector)::iterator iter;
Alternativamente:
decltype(intVector.begin()) iter;
https://riptutorial.com/es/home 241
En el segundo ejemplo, el tipo de retorno de begin se usa para determinar el tipo real, que es
vector<int>::iterator .
https://riptutorial.com/es/home 242
Capítulo 34: deducción de tipo
Observaciones
En noviembre de 2014, el Comité de Normalización de C ++ adoptó la propuesta N3922, que
elimina la regla de deducción de tipo especial para los inicializadores automáticos y con refuerzo
mediante la sintaxis de inicialización directa. Esto no es parte del estándar C ++ pero ha sido
implementado por algunos compiladores.
Examples
Deducción de parámetros de plantilla para constructores.
Antes de C ++ 17, la deducción de la plantilla no puede deducir el tipo de clase para usted en un
constructor. Debe especificarse explícitamente. A veces, sin embargo, estos tipos pueden ser
muy engorrosos o (en el caso de las lambdas) imposibles de nombrar, por lo que tenemos una
proliferación de fábricas de tipos (como make_pair() , make_tuple() , back_inserter() , etc.).
C ++ 17
Esto ya no es necesario:
Se considera que los constructores deducen los parámetros de la plantilla de clase, pero en
algunos casos esto no es suficiente y podemos proporcionar guías de deducción explícitas:
template<typename T>
void f(ParamType param);
f(expr);
Caso 1: ParamType es una referencia o un puntero, pero no una referencia universal o directa. En
https://riptutorial.com/es/home 243
este caso, la deducción de tipos funciona de esta manera. El compilador ignora la parte de
referencia si existe en expr . El compilador entonces Patronistas los partidos expr tipo 's contra
ParamType a determing T .
template<typename T>
void f(T& param); //param is a reference
Caso 2: ParamType es una referencia universal o una referencia directa. En este caso, la deducción
de tipo es la misma que en el caso 1 si expr es un valor nominal. Si expr es un valor límico, tanto T
como ParamType se deducen como referencias de valor l.
template<typename T>
void f(T&& param); // param is a universal reference
Caso 3: ParamType es un puntero ni una referencia. Si expr es una referencia, la parte de referencia
se ignora. Si expr es const, se ignora también. Si es volátil, eso también se ignora al deducir el
tipo de T.
template<typename T>
void f(T param); // param is now passed by value
C ++ 11
La deducción de tipo usando la palabra clave auto funciona casi igual que la Deducción de tipo de
plantilla. Abajo hay algunos ejemplos:
https://riptutorial.com/es/home 244
auto x = 27; // (x is neither a pointer nor a reference), x's type is int
const auto cx = x; // (cx is neither a pointer nor a reference), cs's type is const int
const auto& rx = x; // (rx is a non-universal reference), rx's type is a reference to a
const int
Como puede ver si usa inicializadores con refuerzos, se fuerza automáticamente a crear una
variable de tipo std::initializer_list<T> . Si no puede deducir el valor de T , el código es
rechazado.
Cuando se utiliza auto como el tipo de retorno de una función, especifica que la función tiene un
tipo de retorno final .
C ++ 14
1. Cuando se usa como el tipo de retorno de una función sin un tipo de retorno final, especifica
que el tipo de retorno de la función debe deducirse de las declaraciones de retorno en el
cuerpo de la función, si corresponde.
// f returns int:
auto f() { return 42; }
// g returns void:
auto g() { std::cout << "hello, world!\n"; }
La forma especial decltype(auto) deduce un tipo utilizando las reglas de deducción de decltype de
decltype lugar de las de auto .
https://riptutorial.com/es/home 245
int* p = new int(42);
auto x = *p; // x has type int
decltype(auto) y = *p; // y is a reference to *p
https://riptutorial.com/es/home 246
Capítulo 35: Devolviendo varios valores de
una función
Introducción
Hay muchas situaciones en las que es útil devolver varios valores de una función: por ejemplo, si
desea ingresar un artículo y devolver el precio y el número en stock, esta funcionalidad podría ser
útil. Hay muchas formas de hacer esto en C ++, y la mayoría involucra a la STL. Sin embargo, si
desea evitar el STL por alguna razón, todavía hay varias formas de hacer esto, incluyendo
structs/classes y arrays .
Examples
Uso de parámetros de salida
Los parámetros se pueden usar para devolver uno o más valores; Esos parámetros deben ser
punteros o referencias no const .
Referencias:
Punteros:
Algunas bibliotecas o marcos de trabajo usan un #define 'vacío' para #define que los parámetros
sean parámetros de salida en la firma de la función. Esto no tiene un impacto funcional y se
compilará, pero hace que la firma de la función sea un poco más clara;
#define OUT
https://riptutorial.com/es/home 247
Usando std :: tuple
C ++ 11
El tipo std::tuple puede agrupar cualquier número de valores, posiblemente incluyendo valores
de diferentes tipos, en un solo objeto de retorno:
C ++ 17
Si los tipos se pueden declarar antes de que se devuelva la función, entonces se puede emplear
std::tie para descomprimir una tuple en variables existentes:
C ++ 17
Si desea devolver una tupla de referencias de lvalor en lugar de una tupla de valores, use
std::tie en lugar de std::make_tuple .
https://riptutorial.com/es/home 248
else
return std::tie(a,b);
}
lo que permite
En algunos casos raros std::forward_as_tuple lugar de std::tie ; tenga cuidado si lo hace, ya que
los temporales pueden no durar lo suficiente como para ser consumidos.
C ++ 11
El contenedor std::array puede agrupar un número fijo de valores de retorno. Este número debe
conocerse en tiempo de compilación y todos los valores de retorno deben ser del mismo tipo:
Esto reemplaza los arrays de estilo c de la int bar[4] form int bar[4] . La ventaja es que ahora se
pueden usar varias funciones std de c++ . También proporciona funciones útiles como miembros
at que es una función de acceso miembro de seguro con la comprobación de encuadernado, y
size que le permite devolver el tamaño de la matriz sin cálculo.
La estructura struct template std::pair puede agrupar exactamente dos valores de retorno, de
cualquiera de los dos tipos:
#include <utility>
std::pair<int, int> foo(int a, int b) {
return std::make_pair(a+b, a-b);
}
C ++ 11
#include <utility>
std::pair<int, int> foo(int a, int b) {
return {a+b, a-b};
}
Los valores individuales del std::pair devuelto se pueden recuperar utilizando los objetos
miembros first y second del par:
https://riptutorial.com/es/home 249
std::pair<int, int> mrvs = foo(5, 12);
std::cout << mrvs.first + mrvs.second << std::endl;
Salida:
10
Usando struct
C ++ 11
struct foo_return_type {
int add;
int sub;
int mul;
int div;
};
C ++ 11
struct foo_return_type {
int add;
int sub;
int mul;
int div;
foo_return_type(int add, int sub, int mul, int div)
: add(add), sub(sub), mul(mul), div(div) {}
};
Los resultados individuales devueltos por la función foo() se pueden recuperar accediendo a las
variables miembro de la struct calc :
std::cout << calc.add << ' ' << calc.sub << ' ' << calc.mul << ' ' << calc.div << '\n';
Salida:
17 -7 60 0
https://riptutorial.com/es/home 250
Nota: cuando se utiliza una struct , los valores devueltos se agrupan en un solo objeto y se puede
acceder a ellos utilizando nombres significativos. Esto también ayuda a reducir el número de
variables extrañas creadas en el alcance de los valores devueltos.
C ++ 17
Para desempaquetar una struct devuelta desde una función, se pueden usar enlaces
estructurados . Esto coloca los parámetros de salida en una posición uniforme con los parámetros
de entrada:
La salida de este código es idéntica a la anterior. La struct todavía se utiliza para devolver los
valores de la función. Esto le permite hacer frente a los campos de forma individual.
Encuadernaciones Estructuradas
C ++ 17
C ++ 17 introduce enlaces estructurados, lo que hace que sea aún más fácil tratar con múltiples
tipos de devolución, ya que no es necesario confiar en std::tie() ni realizar ningún
desempaquetado manual de tuplas:
std::map<std::string, int> m;
if (success) {
// your code goes here
}
// iterate over all elements without having to use the cryptic 'first' and 'second' names
for (auto const& [key, value] : m) {
std::cout << "The value for " << key << " is " << value << '\n';
}
Los enlaces estructurados se pueden usar de forma predeterminada con std::pair , std::tuple y
cualquier tipo cuyos miembros de datos no estáticos sean todos miembros públicos directos o
miembros de una clase base no ambigua:
struct A { int x; };
struct B : A { int y; };
B foo();
https://riptutorial.com/es/home 251
auto& y = result.y;
Si hace que su tipo sea "similar a una tupla", también funcionará automáticamente con su tipo. Un
tuple-like es un tipo con tuple_size , tuple_element y get tuple_element :
namespace my_ns {
struct my_type {
int x;
double d;
std::string s;
};
struct my_type_view {
my_type* ptr;
};
}
namespace std {
template<>
struct tuple_size<my_ns::my_type_view> : std::integral_constant<std::size_t, 3>
{};
namespace my_ns {
template<std::size_t I>
decltype(auto) get(my_type_view const& v) {
if constexpr (I == 0)
return v.ptr->x;
else if constexpr (I == 1)
return v.ptr->d;
else if constexpr (I == 2)
return v.ptr->s;
static_assert(I < 3, "Only 3 elements");
}
}
my_ns::my_type_view foo() {
return {&t};
}
int main() {
auto[x, d, s] = foo();
std::cout << x << ',' << d << ',' << s << '\n';
}
Podemos proporcionar un consumidor al que se llamará con los múltiples valores relevantes:
C ++ 11
https://riptutorial.com/es/home 252
template <class F>
void foo(int a, int b, F consumer) {
consumer(a + b, a - b, a * b, a / b);
}
Puede adaptar una función devolviendo una tupla a una función de estilo de paso de continuación
a través de:
C ++ 17
template<class Tuple>
struct continuation {
Tuple t;
template<class F>
decltype(auto) operator->*(F&& f)&&{
return std::apply( std::forward<F>(f), std::move(t) );
}
};
std::tuple<int,int,int,int> foo(int a, int b);
Un std::vector puede ser útil para devolver un número dinámico de variables del mismo tipo. El
siguiente ejemplo usa int como tipo de datos, pero un std::vector puede contener cualquier tipo
que se pueda copiar de forma trivial:
#include <vector>
#include <iostream>
// the following function returns all integers between and including 'a' and 'b' in a vector
// (the function can return up to std::vector::max_size elements with the vector, given that
// the system's main memory can hold that many items)
std::vector<int> fillVectorFrom(int a, int b) {
std::vector<int> temp;
for (int i = a; i <= b; i++) {
temp.push_back(i);
}
return temp;
}
int main() {
// assigns the filled vector created inside the function to the new vector 'v'
std::vector<int> v = fillVectorFrom(1, 10);
https://riptutorial.com/es/home 253
// prints "1 2 3 4 5 6 7 8 9 10 "
for (int i = 0; i < v.size(); i++) {
std::cout << v[i] << " ";
}
std::cout << std::endl;
return 0;
}
Se pueden devolver varios valores del mismo tipo pasando un iterador de salida a la función. Esto
es particularmente común para funciones genéricas (como los algoritmos de la biblioteca
estándar).
Ejemplo:
Ejemplo de uso:
std::vector<int> digits;
generate_sequence(0, 10, std::back_inserter(digits));
// digits now contains {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
https://riptutorial.com/es/home 254
Capítulo 36: Diseño de tipos de objetos
Observaciones
Ver también Tamaño de tipos integrales .
Examples
Tipos de clase
Por "clase", nos referimos a un tipo que se definió usando la palabra clave class o struct (pero no
enum struct o enum class ).
• Incluso una clase vacía aún ocupa al menos un byte de almacenamiento; Por lo tanto,
consistirá puramente de relleno. Esto garantiza que si p apunta a un objeto de una clase
vacía, p + 1 es una dirección distinta y apunta a un objeto distinto. Sin embargo, es posible
que una clase vacía tenga un tamaño de 0 cuando se utiliza como clase base. Ver la
optimización de la base vacía .
struct S {
int x;
char* y;
};
• Si un tipo de clase tiene miembros y / o clases base con los tipos t1, t2,...tN , el tamaño
debe ser al menos sizeof(t1) + sizeof(t2) + ... + sizeof(tN) dados los puntos anteriores .
Sin embargo, dependiendo de los requisitos de alineación de los miembros y las clases
base, el compilador puede verse obligado a insertar relleno entre subobjetos, o al principio o
al final del objeto completo.
https://riptutorial.com/es/home 255
// sizeof(AnInt) == sizeof(int)
// Assuming a typical 32- or 64-bit system, sizeof(AnInt) == 4 (4).
struct TwoInts { int i, j; };
// sizeof(TwoInts) >= 2 * sizeof(int)
// Assuming a typical 32- or 64-bit system, sizeof(TwoInts) == 8 (4 + 4).
struct IntAndChar { int i; char c; };
// sizeof(IntAndChar) >= sizeof(int) + sizeof(char)
// Assuming a typical 32- or 64-bit system, sizeof(IntAndChar) == 8 (4 + 1 +
padding).
struct AnIntDerived : AnInt { long long l; };
// sizeof(AnIntDerived) >= sizeof(AnInt) + sizeof(long long)
// Assuming a typical 32- or 64-bit system, sizeof(AnIntDerived) == 16 (4 + padding +
8).
https://riptutorial.com/es/home 256
// Assume sizeof(bool) == 1, sizeof(ShortIntCharInt) == 16, and sizeof(IntLLInt) == 24.
// Assume default alignment: alignof(ShortIntCharInt) == 4, alignof(IntLLInt) == 8.
struct ShortChar3ArrShortInt {
short s;
char c3[3];
short t;
int i;
};
// ShortChar3ArrShortInt has 4-byte alignment: alignof(int) >= alignof(char) &&
// alignof(int) >= alignof(short)
// sizeof(ShortChar3ArrShortInt) == 12 (2 (short) + 3 (char[3]) + 1 (padding) +
// 2 (short) + 4 (int))
// Note that t is placed at alignment of 2, not 4. alignof(short) == 2.
struct Large_1 {
ShortIntCharInt sici;
bool b;
ShortIntCharInt tjdj;
};
// Large_1 has 4-byte alignment.
// alignof(ShortIntCharInt) == alignof(int) == 4
// alignof(b) == 1
// Therefore, alignof(Large_1) == 4.
// sizeof(Large_1) == 36 (16 (ShortIntCharInt) + 1 (bool) + 3 (padding) +
// 16 (ShortIntCharInt))
struct Large_2 {
IntLLInt illi;
float f;
IntLLInt jmmj;
};
// Large_2 has 8-byte alignment.
// alignof(IntLLInt) == alignof(long long) == 8
// alignof(float) == 4
// Therefore, alignof(Large_2) == 8.
// sizeof(Large_2) == 56 (24 (IntLLInt) + 4 (float) + 4 (padding) + 24 (IntLLInt))
C ++ 11
• Si se fuerza una alineación estricta con las alineaciones, se alignas relleno para forzar al
tipo a cumplir con la alineación especificada, incluso cuando de lo contrario sería más
pequeño. Por ejemplo, con la definición a continuación, los caracteres Chars<5> tendrán tres
(o posiblemente más) bytes de relleno insertados al final, de modo que su tamaño total sea
8. No es posible que una clase con una alineación de 4 tenga un tamaño. de 5 porque sería
imposible hacer una matriz de esa clase, por lo que el tamaño debe "redondearse" a un
múltiplo de 4 insertando bytes de relleno.
https://riptutorial.com/es/home 257
entonces se garantiza que el que viene más tarde en el orden de declaración vendrá más
adelante en la representación del objeto. Pero si dos miembros no estáticos tienen
diferentes especificadores de acceso, su orden relativo dentro del objeto no se especifica.
• No se especifica en qué orden aparecen los subobjetos de la clase base dentro de un
objeto, ya sea que aparezcan consecutivamente, y si aparecen antes, después o entre
subobjetos miembros.
Tipos aritméticos
El tipo signed char no tiene bits de relleno, es decir, si el signed char tiene una longitud de 8 bits,
tiene 8 bits de capacidad para representar un número.
Tenga en cuenta que estas garantías no se aplican a tipos que no sean tipos de caracteres
estrechos.
Tipos enteros
Los tipos de enteros sin signo utilizan un sistema binario puro, pero pueden contener bits de
relleno. Por ejemplo, es posible (aunque improbable) que un unsigned int tenga una longitud de
64 bits pero solo sea capaz de almacenar enteros entre 0 y 2 32 - 1, ambos inclusive. Los otros 32
bits serían bits de relleno, que no deberían escribirse directamente.
Los tipos de enteros con signo utilizan un sistema binario con un bit de signo y posiblemente bits
de relleno. Los valores que pertenecen al rango común de un tipo entero con signo y el tipo
entero sin signo correspondiente tienen la misma representación. Por ejemplo, si el patrón de bits
0001010010101011 de un objeto unsigned short representa el valor 5291 , entonces también
representa el valor 5291 cuando se interpreta como un objeto short .
https://riptutorial.com/es/home 258
signo). Sin embargo, la norma no garantiza nada. Los tipos de punto flotante a menudo tienen
"representaciones de trampa", que causan errores cuando se usan en los cálculos.
Arrays
Un tipo de matriz no tiene relleno entre sus elementos. Por lo tanto, una matriz con el tipo de
elemento T es solo una secuencia de objetos T dispuestos en memoria, en orden.
Una matriz multidimensional es una matriz de matrices, y lo anterior se aplica recursivamente. Por
ejemplo, si tenemos la declaración.
int a[5][3];
entonces a es una matriz de 5 matrices de 3 int s. Por lo tanto, a[0] , que consiste en los tres
elementos a[0][0] , a[0][1] , a[0][2] , se presenta en la memoria antes de a[1] , que consiste en
de a[1][0] , a[1][1] , y a[1][2] . Esto se llama fila orden mayor .
https://riptutorial.com/es/home 259
Capítulo 37: Ejemplos de servidor cliente
Examples
Hola servidor TCP
Permítanme comenzar diciendo que primero deben visitar la Guía de programación de red de
Beej y leerlo rápidamente, lo que explica la mayoría de estas cosas con más detalle. Aquí
crearemos un servidor TCP simple que dirá "Hola mundo" a todas las conexiones entrantes y
luego las cerraremos. Otra cosa a tener en cuenta es que el servidor se comunicará de forma
iterativa con los clientes, lo que significa un cliente a la vez. Asegúrese de revisar las páginas de
manual relevantes, ya que pueden contener información valiosa sobre cada llamada de función y
estructuras de socket.
Ejecutaremos el servidor con un puerto, así que también tomaremos un argumento para el
número de puerto. Empecemos con el código
addrinfo hints, *res, *p; // we need 2 pointers, res to hold and p to iterate over
memset(&hints, 0, sizeof(hints));
// man getaddrinfo
int gAddRes = getaddrinfo(NULL, portNum, &hints, &res);
if (gAddRes != 0) {
https://riptutorial.com/es/home 260
std::cerr << gai_strerror(gAddRes) << "\n";
return -2;
}
// if no addresses found :(
if (!numOfAddr) {
std::cerr << "Found no host address to use\n";
return -3;
}
https://riptutorial.com/es/home 261
p = res;
// if some error occurs, make sure to close socket and free resources
close(sockFD);
freeaddrinfo(res);
return -5;
}
// if some error occurs, make sure to close socket and free resources
close(sockFD);
freeaddrinfo(res);
return -6;
}
// send call sends the data you specify as second param and it's length as 3rd param,
also returns how many bytes were actually sent
auto bytes_sent = send(newFD, response.data(), response.length(), 0);
https://riptutorial.com/es/home 262
close(newFD);
}
close(sockFD);
freeaddrinfo(res);
return 0;
}
Detecting addresses
(1) IPv4 : 0.0.0.0
(2) IPv6 : ::
Enter the number of host address to bind with: 1
Este programa es complementario al programa Hello TCP Server, puede ejecutar cualquiera de
ellos para verificar la validez de los demás. El flujo de programas es bastante común con el
servidor Hello TCP, así que asegúrese de echarle un vistazo a eso también.
#include <cstring>
#include <iostream>
#include <string>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
https://riptutorial.com/es/home 263
}
if (p == NULL) {
std::cerr << "No addresses found\n";
return -3;
}
return 0;
}
https://riptutorial.com/es/home 264
Capítulo 38: El estándar ISO C ++
Introducción
En 1998, hubo una primera publicación de la norma que convierte a C ++ en un lenguaje
estandarizado internamente. A partir de ese momento, C ++ ha evolucionado dando como
resultado diferentes dialectos de C ++. En esta página, puede encontrar una descripción general
de todos los diferentes estándares y sus cambios en comparación con la versión anterior. Los
detalles sobre cómo usar estas funciones se describen en páginas más especializadas.
Observaciones
Cuando se menciona C ++, a menudo se hace referencia al "Estándar". Pero, ¿qué es
exactamente ese estándar?
C ++ tiene una larga historia. Comenzó como un pequeño proyecto de Bjarne Stroustrup dentro
de los Laboratorios Bell, a principios de los 90 se había vuelto bastante popular. Varias
compañías creaban compiladores de C ++ para que los usuarios pudieran ejecutar sus
compiladores de C ++ en una amplia gama de computadoras. Pero para facilitar esto, todos estos
compiladores que compiten deberían compartir una sola definición del lenguaje.
En ese momento, el lenguaje C se había estandarizado con éxito. Esto significa que se escribió
una descripción formal del lenguaje. Este se presentó al American National Standards Institute
(ANSI), que abrió el documento para su revisión y posteriormente lo publicó en 1989. Un año
después, la Organización Internacional de Estándares (ya que tendría diferentes siglas en
diferentes idiomas, eligieron un formulario). , ISO, derivado de las islas griegas, que significa
igual.) Adoptó la norma estadounidense como norma internacional.
Para C ++, estaba claro desde el principio que había un interés internacional. Se inició un grupo
de trabajo dentro de ISO (conocido como WG21, dentro del Subcomité 22). Este grupo de trabajo
elaboró un primer estándar alrededor de 1995. Pero como sabemos los programadores, no hay
nada más peligroso para una entrega planificada que las funciones de última hora, y eso también
le sucedió a C ++. En 1995, surgió una nueva biblioteca llamada STL, y las personas que trabajan
en WG21 decidieron agregar una versión reducida al estándar borrador de C ++. Naturalmente,
esto hizo que los plazos se perdieran y solo 3 años después el documento se convirtió en
definitivo. ISO es una organización muy formal, por lo que el Estándar C ++ fue bautizado con el
nombre poco comercial de ISO / IEC 14882. Como los estándares pueden actualizarse, esta
versión exacta se conoció como 14882: 1998.
Y de hecho hubo una demanda para actualizar el Estándar. El estándar es un documento muy
grueso, cuyo objetivo es describir exactamente cómo deberían funcionar los compiladores de C
++. Incluso vale la pena arreglar una pequeña ambigüedad, por lo que para 2003 se lanzó una
actualización como 14882: 2003. Sin embargo, esto no agregó ninguna característica a C ++; Las
nuevas características fueron programadas para la segunda actualización.
https://riptutorial.com/es/home 265
De manera informal, esta segunda actualización se conoció como C ++ 0x, porque no se sabía si
duraría hasta 2008 o 2009. Bueno, esa versión también tuvo un ligero retraso, por lo que se
convirtió en 14882: 2011.
Por suerte, WG21 decidió no dejar que eso volviera a suceder. C ++ 11 fue bien recibido y
permitió un renovado interés en C ++. Entonces, para mantener el impulso, la tercera
actualización pasó de la planificación a la publicación en 3 años, para convertirse en 14882: 2014.
Examples
Borradores de trabajo actuales
Todas las normas ISO publicadas están disponibles para la venta en la tienda ISO (
http://www.iso.org ). Sin embargo, los borradores de trabajo de los estándares de C ++ están
disponibles públicamente de forma gratuita.
C ++ 11
Extensiones de lenguaje
Características generales
• auto
• decltype
• Declaración de rango de
• Listas de inicialización
• Sintaxis de inicialización uniforme y semántica.
https://riptutorial.com/es/home 266
• Referencias de valores y semánticas de movimiento.
• Lambdas
• noexcept para prevenir la propagación de excepciones
• constexpr
• nullptr - un puntero nulo literal
• Copiar y volver a emitir excepciones.
• Espacios de nombres en línea
• Literales definidos por el usuario
Las clases
• = predeterminado y = eliminar
• Control de movimiento por defecto y copia.
• Constructores delegantes
• Inicializadores de miembros en clase
• Constructores heredados
• Anular controles: anular
• Controles de anulación: final
• Operadores de conversión explícita
Otros tipos
• clase de enumeración
• long long - un entero más largo
• Tipos enteros extendidos
• Sindicatos generalizados
• POD generalizados
Plantillas
• Plantillas externas
• Alias de plantillas
• Plantillas variables
• Tipos locales como argumentos de plantilla
Concurrencia
• Modelo de memoria concurrente
• Inicialización dinámica y destrucción con concurrencia.
• Almacenamiento de hilo local
https://riptutorial.com/es/home 267
• Sintaxis de tipo de retorno de sufijo
• Previniendo el estrechamiento
• Corchetes de ángulo recto
• static_assert aserciones en tiempo de compilación
• Literales de cuerda cruda
• Atributos
• Alineación
• Características C99
Extensiones de biblioteca
General
• unique_ptr
• shared_ptr
• débil_ptr
• Recolección de basura abi
• tupla
• Tipo de rasgos
• función y enlace
• Expresiones regulares
• Utilidades de tiempo
• Generación de números aleatorios
• Asignadores de ámbito
Contenedores y algoritmos
• Mejoras de algoritmos.
• Mejoras de contenedores
• desordenados_ * contenedores
• std :: array
• forward_list
Concurrencia
• Trapos
• Exclusión mutua
• Cabellos
• Variables de condicion
• Atomística
• Futuros y promesas
• asíncrono
• Abandonando un proceso
https://riptutorial.com/es/home 268
C ++ 14
El estándar C ++ 14 a menudo se conoce como una corrección de errores para C ++ 11. Contiene
solo una lista limitada de cambios, de los cuales la mayoría son extensiones de las nuevas
funciones en C ++ 11. A continuación, puede encontrar una descripción general de los cambios,
ya que se han agrupado en las Preguntas frecuentes de isocpp con enlaces a documentación
más detallada.
Extensiones de lenguaje
• Literales binarios
• Deducción del tipo de retorno generalizado.
• decltype (auto)
• Capturas de lambda generalizadas
• Lambdas genericas
• Plantillas variables
• constexpr extendido
• El atributo [[deprecated]]
• Separadores de dígitos
Extensiones de biblioteca
• Bloqueo compartido
• Literales definidos por el usuario para std:: types
• std::make_unique
• Tipo de transformación _t alias
• Abordar las tuplas por tipo (por ejemplo, get<string>(t) )
• Funciones de operador transparentes (por ejemplo, greater<>(x) )
• std::quoted
En desuso / Eliminado
• std::gets fue desaprobado en C ++ 11 y eliminado de C ++ 14
• std::random_shuffle está en desuso
C ++ 17
Extensiones de lenguaje
https://riptutorial.com/es/home 269
• Expresiones Fold
• declarando argumentos de plantilla que no son de tipo con auto
• Copia garantizada elision
• Deducción de parámetros de plantilla para constructores.
• Enlaces estructurados
• Espacios de nombres anidados compactos
• Nuevos atributos: [[fallthrough]] , [[nodiscard]] , [[maybe_unused]]
• Mensaje predeterminado para static_assert
• Inicializadores en if y switch
• Variables en linea
• if constexpr
• Garantías de orden de expresión.
• Asignación de memoria dinámica para datos sobre alineados
Extensiones de biblioteca
• std::optional
• std::variant
• std::string_view
• merge() y extract() para contenedores asociativos
• Una biblioteca de sistema de archivos con el encabezado <filesystem> .
• Versiones paralelas de la mayoría de los algoritmos estándar (en el encabezado <algorithm>
).
• Adición de funciones matemáticas especiales en el encabezado <cmath> .
• Mover nodos entre map <>, unordered_map <>, set <> y unordered_set <>
C ++ 03
El estándar C ++ 03 trata principalmente los informes de defectos del estándar C ++ 98. Aparte de
estos defectos, solo agrega una nueva característica.
Extensiones de lenguaje
• Inicialización del valor
C ++ 98
https://riptutorial.com/es/home 270
• Clases, Clases derivadas, funciones miembro virtuales, funciones miembro const.
• Sobrecarga de funciones, Sobrecarga del operador.
• Comentarios de una sola línea (se ha introducido en el C-languague con el estándar C99)
• Referencias
• nuevo y borrar
• tipo booleano (ha sido introducido en el C-languague con el estándar C99)
• plantillas
• espacios de nombres
• excepciones
• moldes específicos
Extensiones de biblioteca
• La biblioteca de plantillas estándar
C ++ 20
Las siguientes funciones son simplemente lo que se ha aceptado para la próxima versión del
estándar C ++, orientado para 2020.
Extensiones de lenguaje
No se han aceptado extensiones de idioma por ahora.
Extensiones de biblioteca
No se han aceptado extensiones de biblioteca por ahora.
https://riptutorial.com/es/home 271
Capítulo 39: El puntero este
Observaciones
thispuntero es una palabra clave para C ++, por lo que no se necesita una biblioteca para
implementar esto. Y no olvides que this es un puntero! Así que no puedes hacer:
this.someMember();
A medida que accede a las funciones o variables de los miembros desde los punteros, utilice el
símbolo de flecha -> :
this->someMember();
http://www.geeksforgeeks.org/this-pointer-in-c/
https://www.tutorialspoint.com/cplusplus/cpp_this_pointer.htm
Examples
este puntero
Todas las funciones miembro no estáticas tienen un parámetro oculto, un puntero a una instancia
de la clase, llamado this ; este parámetro se inserta silenciosamente al principio de la lista de
parámetros, y se maneja por completo por el compilador. Cuando se accede a un miembro de la
clase dentro de una función miembro, se accede de forma silenciosa a través de this ; esto
permite que el compilador utilice una única función miembro no estática para todas las instancias,
y permite que una función miembro llame a otras funciones miembro de forma polimórfica.
struct ThisPointer {
int i;
ThisPointer(int ii);
https://riptutorial.com/es/home 272
// valid until the instance is fully constructed.
En un constructor, this se puede usar de manera segura para (implícita o explícitamente) acceder
a cualquier campo que ya se haya inicializado, o cualquier campo en una clase principal; a la
inversa, el acceso (implícito o explícito) a cualquier campo que aún no se haya inicializado, o
cualquier campo en una clase derivada, es inseguro (debido a que la clase derivada aún no se ha
construido y, por lo tanto, sus campos no están inicializados ni existen). Tampoco es seguro
llamar a las funciones miembro virtuales a través de this en el constructor, ya que no se
considerarán las funciones de clase derivadas (debido a que la clase derivada aún no se ha
construido y, por lo tanto, su constructor aún no actualiza el vtable).
class CtorThisBase {
short s;
public:
CtorThisBase() : s(516) {}
};
public:
https://riptutorial.com/es/home 273
// Good constructor.
CtorThis() : i(s + 42), j(this->i), k(j) {}
// Bad constructor.
CtorThis(int ii) : i(ii), j(this->k), k(b ? 51 : -51) {
virt_func();
}
public:
CtorThisDerived() : b(true) {}
CtorThisDerived(int ii) : CtorThis(ii), b(false) {}
// ...
CtorThisDerived ctd_good;
CtorThisDerived ctd_bad(3);
En este contexto, el uso de this puntero no es completamente necesario, pero hará que su
código sea más claro para el lector, al indicar que una función o variable determinada es un
miembro de la clase. Un ejemplo en esta situación:
https://riptutorial.com/es/home 274
// Example for this pointer
#include <iostream>
#include <string>
using std::cout;
using std::endl;
class Class
{
public:
Class();
~Class();
int getPrivateNumber () const;
private:
int private_number = 42;
};
Class::Class(){}
Class::~Class(){}
int main()
{
Class class_example;
cout << class_example.getPrivateNumber() << endl;
}
Esta es una estrategia realmente útil para diferenciar los datos de los miembros de los
parámetros ... Tomemos este ejemplo:
using std::cout;
using std::endl;
/*
* @class Dog
* @member name
* Dog's name
* @function bark
* Dog Barks!
* @function getName
* To Get Private
* Name Variable
*/
class Dog
{
public:
https://riptutorial.com/es/home 275
Dog(std::string name);
~Dog();
void bark() const;
std::string getName() const;
private:
std::string name;
};
Dog::Dog(std::string name)
{
/*
* this->name is the
* name variable from
* the class dog . and
* name is from the
* parameter of the function
*/
this->name = name;
}
Dog::~Dog(){}
int main()
{
Dog dog("Max");
cout << dog.getName() << endl;
dog.bark();
}
this->name = name;
Aquí puede ver que estamos asignando el nombre del parámetro al nombre de la variable privada
de la clase Dog (this-> name).
thistambién puede ser calificado como CV, al igual que cualquier otro puntero. Sin embargo,
debido a que this parámetro no aparece en la lista de parámetros, se requiere una sintaxis
especial para esto; los calificadores cv se enumeran después de la lista de parámetros, pero
antes del cuerpo de la función.
https://riptutorial.com/es/home 276
struct ThisCVQ {
void no_qualifier() {} // "this" is: ThisCVQ*
void c_qualifier() const {} // "this" is: const ThisCVQ*
void v_qualifier() volatile {} // "this" is: volatile ThisCVQ*
void cv_qualifier() const volatile {} // "this" is: const volatile ThisCVQ*
};
Como this es un parámetro, una función puede ser sobrecargado basado en su this cv-
calificador (s) .
struct CVOverload {
int func() { return 3; }
int func() const { return 33; }
int func() volatile { return 333; }
int func() const volatile { return 3333; }
};
Cuando this es const (incluyendo const volatile ), la función no puede escribir en las variables
miembro a través de él, ya sea de forma implícita o explícita. La única excepción a esto son las
variables miembro mutable , que pueden escribirse independientemente de la constancia. Debido
a esto, const se utiliza para indicar que la función miembro no cambia el estado lógico del objeto
(la forma en que el objeto aparece en el mundo exterior), incluso si modifica el estado físico (la
forma en que el objeto se ve debajo del capó ).
El estado lógico es la forma en que el objeto aparece ante los observadores externos.
No está directamente relacionado con el estado físico y, de hecho, puede que ni
siquiera se almacene como estado físico. Mientras los observadores externos no
puedan ver ningún cambio, el estado lógico es constante, incluso si volteas cada bit en
el objeto.
El estado físico, también conocido como estado a nivel de bits, es la forma en que el
objeto se almacena en la memoria. Este es el meollo del objeto, los 1s y 0s en bruto
que componen sus datos. Un objeto solo es físicamente constante si su
representación en la memoria nunca cambia.
class DoSomethingComplexAndOrExpensive {
mutable ResultType cached_result;
mutable bool state_changed;
ResultType calculate_result();
void modify_somehow(const Param& p);
// ...
public:
DoSomethingComplexAndOrExpensive(Param p) : state_changed(true) {
modify_somehow(p);
}
void change_state(Param p) {
https://riptutorial.com/es/home 277
modify_somehow(p);
state_changed = true;
}
if (state_changed) {
cached_result = calculate_result();
state_changed = false;
}
return cached_result;
}
Tenga en cuenta que, si bien técnicamente podría usar const_cast en this para que no esté
calificado como const_cast , realmente, REALMENTE no debería, y debería usar mutable lugar. Un
const_cast puede invocar un comportamiento indefinido cuando se usa en un objeto que en
realidad es const , mientras que mutable está diseñado para ser seguro de usar. Sin embargo, es
posible que se encuentre con este código extremadamente antiguo.
class CVAccessor {
int arr[5];
public:
const int& get_arr_element(size_t i) const { return arr[i]; }
int& get_arr_element(size_t i) {
return const_cast<int&>(const_cast<const CVAccessor*>(this)->get_arr_element(i));
}
};
Al igual que con los punteros regulares, si this es volatile (incluyendo const volatile ), se carga
desde la memoria cada vez que se accede, en lugar de ser almacenado en caché. Esto tiene los
mismos efectos en la optimización que declarar cualquier otro puntero volatile , por lo que se
debe tener cuidado.
Tenga en cuenta que si una instancia es cv-cualificado, las únicas funciones miembro que se
permite el acceso a funciones son miembros cuyas this puntero es al menos tan cv-calificada
https://riptutorial.com/es/home 278
como la propia instancia:
struct CVAccess {
void func() {}
void func_c() const {}
void func_v() volatile {}
void func_cv() const volatile {}
};
CVAccess cva;
cva.func(); // Good.
cva.func_c(); // Good.
cva.func_v(); // Good.
cva.func_cv(); // Good.
C ++ 11
De manera similar a this calificadores de CV, también podemos aplicar ref-calificadores a *this .
Los ref-calificadores se utilizan para elegir entre semántica de referencia normal y rvalor, lo que
permite al compilador usar la semántica de copiar o mover según sea más apropiado, y se aplican
a *this lugar de this .
Tenga en cuenta que a pesar de los ref-calificadores que usan la sintaxis de referencia, this sigue
siendo un puntero. También tenga en cuenta que los ref-calificadores no cambian realmente el
tipo de *this ; es más fácil describir y entender sus efectos mirándolos como si lo hicieran.
struct RefQualifiers {
https://riptutorial.com/es/home 279
std::string s;
// Normal version.
void func() & { std::cout << "Accessed on normal instance " << s << std::endl; }
// Rvalue version.
void func() && { std::cout << "Accessed on temporary instance " << s << std::endl; }
// ...
RefQualifiers rf("Fred");
rf.func(); // Output: Accessed on normal instance Fred
RefQualifiers{}.func(); // Output: Accessed on temporary instance The nameless one
Una función miembro no puede tener sobrecargas con y sin ref-calificadores; El programador
tiene que elegir entre uno u otro. Afortunadamente, cv-qualifiers se puede utilizar junto con ref-
qualifiers, lo que permite seguir las reglas de corrección de const .
struct RefCV {
void func() & {}
void func() && {}
void func() const& {}
void func() const&& {}
void func() volatile& {}
void func() volatile&& {}
void func() const volatile& {}
void func() const volatile&& {}
};
https://riptutorial.com/es/home 280
Capítulo 40: Enhebrado
Sintaxis
• hilo()
• hilo (hilo y & otros)
• Hilo explícito (Function && func, Args && ... args)
Parámetros
Parámetro Detalles
Observaciones
Algunas notas:
Examples
Operaciones de hilo
int n;
std::thread thread{ calculateSomething, std::ref(n) };
//Now 'n' has the result of the calculation done in the seperate thread
std::cout << n << '\n';
https://riptutorial.com/es/home 281
También puede detach el hilo, permitiendo que se ejecute libremente:
//The thread will terminate when it is done, or when the main thread returns
No puede pasar una referencia (o una referencia const ) directamente a un hilo porque std::thread
los copiará / moverá. En su lugar, use std::reference_wrapper :
void foo(int& b)
{
b = 10;
}
int a = 1;
std::thread thread{ foo, std::ref(a) }; //'a' is now really passed as reference
thread.join();
std::cout << a << '\n'; //Outputs 10
ComplexObject object;
std::thread thread{ bar, std::cref(object) }; //'object' is passed as const&
thread.join();
std::cout << object.getResult() << '\n'; //Outputs the result
En C ++, los subprocesos se crean utilizando la clase std :: thread. Un hilo es un flujo de
ejecución separado; es análogo a que un ayudante realice una tarea mientras usted realiza otra.
Cuando se ejecuta todo el código en el hilo, termina . Cuando creas un hilo, necesitas pasar algo
para ser ejecutado en él. Algunas cosas que puedes pasar a un hilo:
• Funciones libres
• Funciones miembro
• Objetos funcionales
• Expresiones lambda
Ejemplo de función libre: ejecuta una función en un subproceso separado ( ejemplo en vivo ):
#include <iostream>
https://riptutorial.com/es/home 282
#include <thread>
void foo(int a)
{
std::cout << a << '\n';
}
int main()
{
// Create and execute the thread
std::thread thread(foo, 10); // foo is the function to execute, 10 is the
// argument to pass to it
return 0;
}
#include <iostream>
#include <thread>
class Bar
{
public:
void foo(int a)
{
std::cout << a << '\n';
}
};
int main()
{
Bar bar;
return 0;
}
#include <iostream>
#include <thread>
class Bar
https://riptutorial.com/es/home 283
{
public:
void operator()(int a)
{
std::cout << a << '\n';
}
};
int main()
{
Bar bar;
return 0;
}
#include <iostream>
#include <thread>
int main()
{
auto lambda = [](int a) { std::cout << a << '\n'; };
return 0;
}
std::this_thread es un namespace que tiene funciones para hacer cosas interesantes en el hilo
actual desde la función desde la que se llama.
Función Descripción
https://riptutorial.com/es/home 284
Función Descripción
void foo()
{
//Print this threads id
std::cout << std::this_thread::get_id() << '\n';
}
foo(); //The id of the main thread is printed, should be something like 2420
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(3));
}
void foo()
{
std::this_thread::sleep_until(std::chrono::system_clock::now() + std::chrono::hours(3));
}
std::cout << "We are now located 3 hours after the thread has been called\n";
void foo(int a)
{
for (int i = 0; i < al ++i)
std::this_thread::yield(); //Now other threads take priority, because this thread
//isn't doing anything important
https://riptutorial.com/es/home 285
thread.join();
#include <future>
#include <iostream>
int main() {
auto f = std::async(std::launch::async, square, 8);
std::cout << "square currently running\n"; //do something while square is running
std::cout << "result is " << f.get() << '\n'; //getting the result from square
}
Errores comunes
• std::async devuelve un std::future que contiene el valor de retorno que la función calculará.
Cuando ese future se destruye, espera hasta que se complete el subproceso, haciendo que
su código sea efectivamente único. Esto se pasa por alto fácilmente cuando no necesita el
valor de retorno:
• std::async funciona sin una política de lanzamiento, así que std::async(square, 5); compila
Cuando haces eso, el sistema decide si quiere crear un hilo o no. La idea era que el sistema
elige crear un subproceso a menos que ya esté ejecutando más subprocesos de los que
puede ejecutar de manera eficiente. Desafortunadamente, las implementaciones
comúnmente solo eligen no crear un subproceso en esa situación, por lo que es necesario
anular ese comportamiento con std::launch::async que obliga al sistema a crear un
subproceso.
Cuando se invoca el destructor para std::thread se debe haber realizado una llamada a join() o
https://riptutorial.com/es/home 286
detach() . Si un subproceso no se ha unido o separado, se llamará de forma predeterminada a
std::terminate . Usando RAII , esto es generalmente bastante simple de lograr:
class thread_joiner
{
public:
thread_joiner(std::thread t)
: t_(std::move(t))
{ }
~thread_joiner()
{
if(t_.joinable()) {
t_.join();
}
}
private:
std::thread t_;
}
void perform_work()
{
// Perform some work
}
void t()
{
thread_joiner j{std::thread(perform_work)};
// Do some other calculations while thread is running
} // Thread is automatically joined here
Esto también proporciona una excepción de seguridad; si hubiéramos creado nuestro hilo
normalmente y el trabajo realizado en t() realizando otros cálculos hubiera generado una
excepción, nunca se habría llamado join() en nuestro hilo y nuestro proceso habría terminado.
#include <thread>
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(3));
}
//create 100 thread objects that do nothing
std::thread executors[100];
https://riptutorial.com/es/home 287
// Some code
Sincronizacion basica
std::mutex m;
void worker() {
std::lock_guard<std::mutex> guard(m); // Acquires a lock on the mutex
// Synchronized code here
} // the mutex is automatically released when guard goes out of scope
Con std::lock_guard el mutex está bloqueado durante toda la vida útil del objeto de bloqueo. En
los casos en que necesite controlar manualmente las regiones para el bloqueo, use
std::unique_lock lugar:
std::mutex m;
void worker() {
// by default, constructing a unique_lock from a mutex will lock the mutex
// by passing the std::defer_lock as a second argument, we
// can construct the guard in an unlocked state instead and
// manually lock later.
std::unique_lock<std::mutex> guard(m, std::defer_lock);
// the mutex is not locked yet!
guard.lock();
// critical section
guard.unlock();
// mutex is again released
}
Una variable de condición es una primitiva que se usa junto con un mutex para orquestar la
comunicación entre hilos. Si bien no es la forma exclusiva ni más eficiente de lograr esto, puede
ser una de las más sencillas para aquellos familiarizados con el patrón.
https://riptutorial.com/es/home 288
adquisición.
#include <condition_variable>
#include <cstddef>
#include <iostream>
#include <mutex>
#include <queue>
#include <random>
#include <thread>
int main()
{
std::condition_variable cond;
std::mutex mtx;
std::queue<int> intq;
bool stopped = false;
std::thread producer{[&]()
{
// Prepare a random number generator.
// Our producer will simply push random numbers to intq.
//
std::default_random_engine gen{};
std::uniform_int_distribution<int> dist{};
// All done.
// Acquire the lock, set the stopped flag,
// then inform the consumer.
std::lock_guard<std::mutex> L{mtx};
stopped = true;
cond.notify_one();
}};
std::thread consumer{[&]()
{
do{
std::unique_lock<std::mutex> L{mtx};
cond.wait(L,[&]()
https://riptutorial.com/es/home 289
{
// Acquire the lock only if
// we've stopped or the queue
// isn't empty
return stopped || ! intq.empty();
});
while( ! intq.empty())
{
const auto val = intq.front();
intq.pop();
if(stopped){
// producer has signaled a stop
std::cout << "Consumer is done!" << std::endl;
break;
}
}while(true);
}};
consumer.join();
producer.join();
return 0;
}
C ++ 14
struct tasks {
// the mutex, condition variable and deque form a single
// thread-safe triggered queue of tasks:
std::mutex m;
std::condition_variable v;
// note that a packaged_task<void> can store a packaged_task<R>:
std::deque<std::packaged_task<void()>> work;
// queue( lambda ) will enqueue the lambda into the tasks for the threads
// to use. A future of the type the lambda returns is given to let you get
// the result out.
template<class F, class R=std::result_of_t<F&()>>
std::future<R> queue(F&& f) {
// wrap the function object into a packaged task, splitting
https://riptutorial.com/es/home 290
// execution from the return value:
std::packaged_task<R()> p(std::forward<F>(f));
auto r=p.get_future(); // get the return value before we hand off the task
{
std::unique_lock<std::mutex> l(m);
work.emplace_back(std::move(p)); // store the task<R()> as a task<void()>
}
v.notify_one(); // wake a thread to work on the task
https://riptutorial.com/es/home 291
v.wait(l,[&]{return !work.empty();});
}
f = std::move(work.front());
work.pop_front();
}
// if the task is invalid, it means we are asked to abort:
if (!f.valid()) return;
// otherwise, run the task:
f();
}
}
};
Ejemplo vivo .
C ++ 11
Ejemplo:
void debug_counter() {
thread_local int count = 0;
https://riptutorial.com/es/home 292
Logger::log("This function has been called %d times by this thread", ++count);
}
https://riptutorial.com/es/home 293
Capítulo 41: Entrada / salida básica en c ++
Observaciones
La biblioteca estándar <iostream> define algunas secuencias para entrada y salida:
|stream | description |
|-------|----------------------------------|
|cin | standard input stream |
|cout | standard output stream |
|cerr | standard error (output) stream |
|clog | standard logging (output) stream |
De las cuatro secuencias mencionadas anteriormente, cin se usa básicamente para la entrada del
usuario y otras tres se usan para generar los datos. En general o en la mayoría de los entornos
de codificación, cin ( entrada de consola o entrada estándar) es teclado y cout ( salida de consola
o salida estándar) es monitor.
cin aquí extrae la entrada introducida por el usuario y alimenta en valor variable. El valor se
extrae solo después de que el usuario presiona la tecla ENTER.
cout here toma la cadena que se muestra y la inserta en la salida estándar o monitor
Todos los cuatro corrientes se encuentran en espacio de nombres estándar std por lo que
necesitamos para imprimir std::stream de corriente de stream usarlo.
También hay un manipulador std::endl en el código. Se puede usar solo con flujos de salida.
Inserta el carácter de final de línea '\n' en el flujo y lo vacía. Provoca producción inmediata.
Examples
entrada de usuario y salida estándar
#include <iostream>
int main()
https://riptutorial.com/es/home 294
{
int value;
std::cout << "Enter a value: " << std::endl;
std::cin >> value;
std::cout << "The square of entered value is: " << value * value << std::endl;
return 0;
}
https://riptutorial.com/es/home 295
Capítulo 42: Enumeración
Examples
Declaración de enumeración básica
Las enumeraciones estándar permiten a los usuarios declarar un nombre útil para un conjunto de
enteros. Los nombres se conocen colectivamente como enumeradores. Una enumeración y sus
enumeradores asociados se definen de la siguiente manera:
enum myEnum
{
enumName1,
enumName2,
};
Una enumeración es un tipo , uno que es distinto de todos los otros tipos. En este caso, el
nombre de este tipo es myEnum . Se espera que los objetos de este tipo asuman el valor de un
enumerador dentro de la enumeración.
Los enumeradores declarados dentro de la enumeración son valores constantes del tipo de
enumeración. Aunque los enumeradores están declarados dentro del tipo, el operador de alcance
:: no es necesario para acceder al nombre. Entonces el nombre del primer enumerador es
enumName1 .
C ++ 11
A los enumeradores se les asignan valores enteros que comienzan desde 0 y aumentan en 1
para cada enumerador en una enumeración. Entonces, en el caso anterior, enumName1 tiene el valor
0, mientras que enumName2 tiene el valor 1.
Los usuarios también pueden asignar un valor específico al usuario; este valor debe ser una
expresión constante constante. Los enumeradores cuyos valores no se proporcionan
explícitamente tendrán su valor establecido en el valor del enumerador anterior + 1.
enum myEnum
{
enumName1 = 1, // value will be 1
enumName2 = 2, // value will be 2
enumName3, // value will be 3, previous value + 1
enumName4 = 7, // value will be 7
enumName5, // value will be 8
enumName6 = 5, // value will be 5, legal to go backwards
enumName7 = 3, // value will be 3, legal to reuse numbers
enumName8 = enumName4 + 2, // value will be 9, legal to take prior enums and adjust them
};
https://riptutorial.com/es/home 296
Enumeración en declaraciones de cambio
Un uso común para los enumeradores es para las declaraciones de cambio y, por lo tanto, suelen
aparecer en las máquinas de estado. De hecho, una característica útil de las declaraciones de
cambio con enumeraciones es que si no se incluye una declaración predeterminada para el
cambio, y no se han utilizado todos los valores de la enumeración, el compilador emitirá una
advertencia.
enum State {
start,
middle,
end
};
...
switch(myState) {
case start:
...
case middle:
...
} // warning: enumeration value 'end' not handled in switch [-Wswitch]
enum E {
Begin,
E1 = Begin,
E2,
// ..
En,
End
};
C ++ 11
https://riptutorial.com/es/home 297
return e;
}
enum E {
E1 = 4,
E2 = 8,
// ..
En
};
std::vector<E> build_all_E()
{
const E all[] = {E1, E2, /*..*/ En};
y entonces
C ++ 11
enum E {
E1 = 4,
E2 = 8,
// ..
En
};
y entonces
https://riptutorial.com/es/home 298
enum class rainbow {
RED,
ORANGE,
YELLOW,
GREEN,
BLUE,
INDIGO,
VIOLET
};
rainbow r = rainbow::INDIGO;
enum class no se pueden convertir implícitamente a int s sin una conversión. Así que int x =
rainbow::RED no es válido.
Los enums con ámbito también le permiten especificar el tipo subyacente , que es el tipo utilizado
para representar a un miembro. Por defecto es int . En un juego de Tic-Tac-Toe, puedes guardar
la pieza como
Como puede observar, las enum pueden tener una coma final después del último miembro.
Enumeración de ámbito:
...
enum class Status; // Forward declaration
Status doWork(); // Use the forward declaration
...
enum class Status { Invalid, Success, Fail };
Status doWork() // Full declaration required for implementation
{
return Status::Success;
}
...
enum Status: int; // Forward declaration, explicit type required
Status doWork(); // Use the forward declaration
...
enum Status: int{ Invalid=0, Success, Fail }; // Must match forward declare type
static_assert( Success == 1 );
https://riptutorial.com/es/home 299
frutas ciegas
https://riptutorial.com/es/home 300
Capítulo 43: Errores comunes de compilación
/ enlazador (GCC)
Examples
error: '***' no fue declarado en este alcance
Variables
No compilar
#include <iostream>
std::cout << i << std::endl; // i is not in the scope of the main function
return 0;
}
Fijar:
#include <iostream>
return 0;
}
Funciones
La mayoría de las veces, este error se produce si no se incluye el encabezado
necesario (por ejemplo, utilizando std::cout sin #include <iostream> )
No compilar
#include <iostream>
https://riptutorial.com/es/home 301
int main(int argc, char *argv[])
{
doCompile();
return 0;
}
void doCompile()
{
std::cout << "No!" << std::endl;
}
Fijar:
#include <iostream>
return 0;
}
void doCompile()
{
std::cout << "No!" << std::endl;
}
O:
#include <iostream>
return 0;
}
Nota: El compilador interpreta el código de arriba a abajo (simplificación). Todo debe ser al
menos declarado (o definido) antes de su uso.
Este error del enlazador ocurre, si el enlazador no puede encontrar un símbolo usado. La mayoría
de las veces, esto sucede si una biblioteca usada no está vinculada.
qmake:
https://riptutorial.com/es/home 302
LIBS += nameOfLib
cmake:
TARGET_LINK_LIBRARIES(target nameOfLib)
llamada g ++:
También se podría olvidar compilar y vincular todos los archivos .cpp utilizados
(functionsModule.cpp define la función necesaria):
El compilador no puede encontrar un archivo (un archivo de origen usa #include "someFile.hpp" ).
qmake:
INCLUDEPATH += dir/Of/File
cmake:
include_directories(dir/Of/File)
llamada g ++:
https://riptutorial.com/es/home 303
Capítulo 44: Escriba palabras clave
Examples
clase
class foo {
int x;
public:
int get_x();
void set_x(int new_x);
};
https://riptutorial.com/es/home 304
// U is a template template parameter
void f() {
U<int>::do_it();
U<double>::do_it();
}
5. Tenga en cuenta que sense 2 y sense 3 pueden combinarse en la misma declaración. Por
ejemplo:
foo<class bar> x; // <- bar does not have to have previously appeared.
C ++ 11
estructura
enumerar
enum Direction {
UP,
LEFT,
DOWN,
RIGHT
};
Direction d = UP;
C ++ 11
En C ++ 11, enum puede ser seguido opcionalmente por class o struct para definir una
https://riptutorial.com/es/home 305
enumeración de ámbito . Además, las enumeraciones con y sin ámbito pueden tener su tipo
subyacente explícitamente especificado por : T después del nombre de enumeración, donde T
refiere a un tipo entero.
Los enumeradores en enum normales también pueden estar precedidos por el operador del
alcance, aunque todavía se considera que están en el alcance en que se definió la enum .
l1 = ENGLISH;
l2 = Language::OTHER;
C ++ 11
3. Introduce una declaración de enumeración opaca, que declara una enumeración sin
definirla. Puede volver a declarar una enumeración previamente declarada o declarar hacia
adelante una enumeración que no se haya declarado previamente.
Una enumeración que se declara primero como ámbito no se puede declarar más tarde
como sin ámbito, o viceversa. Todas las declaraciones de una enumeración deben coincidir
en el tipo subyacente.
Al declarar hacia adelante una enumeración sin ámbito, el tipo subyacente debe
especificarse explícitamente, ya que no se puede inferir hasta que se conozcan los valores
de los enumeradores.
https://riptutorial.com/es/home 306
enum class Format {
TEXT,
PDF,
OTHER,
};
Unión
https://riptutorial.com/es/home 307
Capítulo 45: Espacios de nombres
Introducción
Utilizado para evitar colisiones de nombres cuando se usan bibliotecas múltiples, un espacio de
nombres es un prefijo declarativo para funciones, clases, tipos, etc.
Sintaxis
• identificador de espacio de nombres ( opt ) { declaración-seq }
• identificador de espacio de nombres en línea ( opt ) { declaraciones-seq } / * desde C ++ 11
*/
• inline ( opt ) namespace atributo-specifier-seq identifier ( opt ) { declaraciones-seq } / * desde
C ++ 17 * /
• espacio de nombres adjuntando-espacio de nombres-especificador :: identificador {
declaración-seq } / * desde C ++ 17 * /
• identificador de espacio de nombres = calificador de espacio de nombres calificado ;
• usando el espacio de nombres nombre-anidado-especificador ( opt ) nombre-espacio-
nombres ;
• atributo-especificador-seq usando el espacio de nombres nombre-anidado-especificador (
opt ) nombre-espacio-nombres ; / * desde C ++ 11 * /
Observaciones
El namespace palabras clave tiene tres significados diferentes según el contexto:
3. Cuando está precedido por el using y seguido de un nombre de espacio de nombres, forma
una directiva de uso , que permite encontrar los nombres en el espacio de nombres dado
por búsqueda de nombre no calificado (pero no vuelve a declarar esos nombres en el
ámbito actual). Una directiva de uso no puede ocurrir en el alcance de la clase.
using namespace std; se desanima ¿Por qué? Porque namespace std es enorme! Esto significa que
hay una alta probabilidad de que los nombres colisionen:
//Really bad!
using namespace std;
https://riptutorial.com/es/home 308
//Calls pow
pow(5.0, 2.0); //Error! There is already a pow function in namespace std with the same
signature,
//so the call is ambiguous
Examples
¿Qué son los espacios de nombres?
namespace Example
{
const int test = 5;
const int test3 = test + 3; //Fails; `test` not found outside of namespace.
Los espacios de nombres son útiles para agrupar definiciones relacionadas. Tomemos la analogía
de un centro comercial. En general, un centro comercial se divide en varias tiendas, cada una de
las cuales vende artículos de una categoría específica. Una tienda podría vender productos
electrónicos, mientras que otra tienda podría vender zapatos. Estas separaciones lógicas en los
tipos de tiendas ayudan a los compradores a encontrar los artículos que están buscando. Los
espacios de nombres ayudan a los programadores de c ++, como los compradores, a encontrar
las funciones, clases y variables que buscan mediante su organización de una manera lógica.
Ejemplo:
namespace Electronics
{
int TotalStock;
class Headphones
{
// Description of a Headphone (color, brand, model number, etc.)
};
class Television
{
// Description of a Television (color, brand, model number, etc.)
};
}
namespace Shoes
{
https://riptutorial.com/es/home 309
int TotalStock;
class Sandal
{
// Description of a Sandal (color, brand, model number, etc.)
};
class Slipper
{
// Description of a Slipper (color, brand, model number, etc.)
};
}
Hay un espacio de nombres único predefinido, que es el espacio de nombres global que no tiene
nombre, pero puede ser denotado por :: . Ejemplo:
void bar() {
// defined in global namespace
}
namespace foo {
void bar() {
// defined in namespace foo
}
void barbar() {
bar(); // calls foo::bar()
::bar(); // calls bar() defined in global namespace
}
}
Para llamar a la bar , primero debe especificar el espacio de nombres, seguido del operador de
resolución de alcance ::
Foo::bar();
namespace A
{
namespace B
{
namespace C
{
void bar() {}
}
}
https://riptutorial.com/es/home 310
}
C ++ 17
namespace A::B::C
{
void bar() {}
}
Una característica útil de los namespace de namespace es que puede expandirlos (agregar miembros
a ellos).
namespace Foo
{
void bar() {}
}
namespace Foo
{
void bar2() {}
}
Usando directiva
La palabra clave 'usar' tiene tres sabores. Combinado con la palabra clave 'espacio de nombres'
se escribe una 'directiva de uso':
Si no quieres escribir Foo:: delante de todas las cosas en el espacio de nombres Foo , puedes
usar using namespace Foo; para importar cada cosa de Foo .
namespace Foo
{
void bar() {}
void baz() {}
}
//Import Foo
using namespace Foo;
bar(); //OK
baz(); //OK
https://riptutorial.com/es/home 311
using Foo::bar;
bar(); //OK, was specifically imported
baz(); // Not OK, was not imported
Cuando se llama a una función sin un calificador de espacio de nombres explícito, el compilador
puede elegir llamar a una función dentro de un espacio de nombres si uno de los tipos de
parámetros para esa función también se encuentra en ese espacio de nombres. Esto se
denomina "búsqueda dependiente de argumentos" o ADL:
namespace Test
{
int call(int i);
Test::SomeClass data;
https://riptutorial.com/es/home 312
call_too(data); //Succeeds
call falla porque ninguno de sus tipos de parámetros proviene del espacio de nombres de Test .
call_too funciona porque SomeClass es miembro de Test y, por lo tanto, califica para las reglas
ADL.
void foo();
namespace N {
struct X {};
void foo(X ) { std::cout << '1'; }
void qux(X ) { std::cout << '2'; }
}
struct C {
void foo() {}
void bar() {
foo(N::X{}); // error: ADL is disabled and C::foo() takes no arguments
}
};
void bar() {
extern void foo(); // redeclares ::foo
foo(N::X{}); // error: ADL is disabled and ::foo() doesn't take any arguments
}
int qux;
void baz() {
qux(N::X{}); // error: variable declaration disables ADL for "qux"
}
C ++ 11
inline namespace incluye el contenido del espacio de nombres en línea en el espacio de nombres
adjunto, por lo que
namespace Outer
{
inline namespace Inner
{
void foo();
}
}
es mayormente equivalente a
https://riptutorial.com/es/home 313
namespace Outer
{
namespace Inner
{
void foo();
}
using Inner::foo;
}
Outer::foo();
Outer::Inner::foo();
La alternativa using namespace Inner; No sería equivalente para algunas partes difíciles como la
especialización de plantillas:
por
class MyCustomType;
namespace Outer
{
template <>
void foo<MyCustomType>() { std::cout << "Specialization"; }
}
// outer.h
// include guard omitted for simplification
namespace Outer
{
inline namespace Inner
{
template <typename T>
void foo() { std::cout << "Generic"; }
}
}
// outer.h
// include guard omitted for simplification
namespace Outer
{
namespace Inner
{
https://riptutorial.com/es/home 314
template <typename T>
void foo() { std::cout << "Generic"; }
}
using namespace Inner;
// Specialization of `Outer::foo` is not possible
// it should be `Outer::Inner::foo`.
}
El espacio de nombres en línea es una forma de permitir que varias versiones cohabiten y por
defecto a la en inline
namespace MyNamespace
{
// Inline the last version
inline namespace Version2
{
void foo(); // New version
void bar();
}
Y con uso
Se puede usar un espacio de nombres sin nombre para garantizar que los nombres tengan un
enlace interno (solo puede hacer referencia la unidad de traducción actual). Dicho espacio de
nombres se define de la misma manera que cualquier otro espacio de nombres, pero sin el
nombre:
namespace {
int foo = 42;
}
Se recomienda nunca utilizar espacios de nombres sin nombre en los archivos de encabezado, ya
que esto proporciona una versión del contenido para cada unidad de traducción en la que se
incluye. Esto es especialmente importante si define globales no constantes.
// foo.h
namespace {
https://riptutorial.com/es/home 315
std::string globalString;
}
// 1.cpp
#include "foo.h" //< Generates unnamed_namespace{1.cpp}::globalString ...
globalString = "Initialize";
// 2.cpp
#include "foo.h" //< Generates unnamed_namespace{2.cpp}::globalString ...
std::cout << globalString; //< Will always print the empty string
C ++ 17
namespace a {
namespace b {
template<class T>
struct qualifies : std::false_type {};
}
}
namespace other {
struct bob {};
}
namespace a::b {
template<>
struct qualifies<::other::bob> : std::true_type {};
}
Puede introducir tanto la a y b espacios de nombres en un solo paso con el namespace a::b
comenzando en C ++ 17.
Por lo general, se usa para cambiar el nombre o acortar referencias largas de nombres de
nombres, como los componentes de una biblioteca.
namespace boost
{
namespace multiprecision
{
class Number ...
}
}
https://riptutorial.com/es/home 316
Alcance de la Declaración de Alias
namespace boost
{
namespace multiprecision
{
class Number ...
}
}
Sin embargo, es más fácil confundirse sobre qué espacio de nombres está creando alias cuando
tiene algo como esto:
namespace boost
{
namespace multiprecision
{
class Number ...
}
}
namespace numeric
{
namespace multiprecision
{
class Number ...
}
}
// Not recommended as
// its not explicitly clear whether Name1 refers to
// numeric::multiprecision or boost::multiprecision
namespace Name1 = multiprecision;
A un espacio de nombres se le puede dar un alias ( es decir, otro nombre para el mismo espacio
de nombres) usando el identificador de namespace = sintaxis. Se puede acceder a los miembros del
espacio de nombres con alias calificándolos con el nombre del alias. En el siguiente ejemplo, el
https://riptutorial.com/es/home 317
espacio de nombres anidado AReallyLongName::AnotherReallyLongName es inconveniente de escribir,
por lo que la función qux declara localmente un alias N Se puede acceder a los miembros de ese
espacio de nombres simplemente usando N:: .
namespace AReallyLongName {
namespace AnotherReallyLongName {
int foo();
int bar();
void baz(int x, int y);
}
}
void qux() {
namespace N = AReallyLongName::AnotherReallyLongName;
N::baz(N::foo(), N::bar());
}
https://riptutorial.com/es/home 318
Capítulo 46: Especificaciones de vinculación
Introducción
Una especificación de vinculación le dice al compilador que compile las declaraciones de una
manera que les permita vincularse con declaraciones escritas en otro idioma, como C.
Sintaxis
• cadena-literal externa { declaración-seq ( opt )}
• declaración cadena-literal externa
Observaciones
El estándar requiere que todos los compiladores admitan la extern "C" para permitir que C ++ sea
compatible con C, y la extern "C++" , que se puede usar para anular una especificación de enlace
adjunto y restaurar la predeterminada. Otras especificaciones de enlace soportadas están
definidas por la implementación .
Examples
Controlador de señal para sistema operativo similar a Unix
https://riptutorial.com/es/home 319
el siguiente foo.h :
La forma de solucionar este problema es envolver casi todas las declaraciones en el encabezado
en un bloque extern "C" .
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
} /* end of "extern C" block */
#endif
https://riptutorial.com/es/home 320
Capítulo 47: Especificadores de clase de
almacenamiento
Introducción
Los especificadores de clase de almacenamiento son palabras clave que se pueden usar en
declaraciones. No afectan el tipo de la declaración, pero generalmente modifican la forma en que
se almacena la entidad.
Observaciones
Hay seis especificadores de clase de almacenamiento, aunque no todos en la misma versión del
idioma: auto (hasta C ++ 11), register (hasta C ++ 17), static , thread_local (desde C ++ 11),
extern y mutable
Según la norma,
Examples
mudable
class C {
int x;
mutable int times_accessed;
public:
C(): x(0), times_accessed(0) {
}
int get_x() const {
++times_accessed; // ok: const member function can modify mutable data member
return x;
}
void set_x(int x) {
++times_accessed;
this->x = x;
}
https://riptutorial.com/es/home 321
};
C ++ 11
Un segundo significado para mutable se añadió en C ++ 11. Cuando sigue la lista de parámetros
de un lambda, suprime la const implícita en el operador de llamada de función del lambda. Por lo
tanto, un lambda mutable puede modificar los valores de las entidades capturadas por copia. Ver
lambdas mutable para más detalles.
registro
C ++ 17
Un especificador de clase de almacenamiento que insinúa al compilador que una variable será
muy utilizada. La palabra "registro" está relacionada con el hecho de que un compilador puede
elegir almacenar una variable de este tipo en un registro de CPU para que se pueda acceder a
ella en menos ciclos de reloj. Fue desaprobado a partir de C ++ 11.
register int i = 0;
while (i < 100) {
f(i);
int g = i*i;
i += h(i, g);
}
Tanto las variables locales como los parámetros de función pueden ser declarados de register . A
diferencia de C, C ++ no impone restricciones a lo que se puede hacer con una variable de
register . Por ejemplo, es válido tomar la dirección de una variable de register , pero esto puede
impedir que el compilador realmente almacene dicha variable en un registro.
C ++ 17
El register palabras clave está sin uso y reservado. Un programa que utiliza el register palabras
clave está mal formado.
estático
1. Da enlace interno a una variable o función declarada en el ámbito del espacio de nombres.
https://riptutorial.com/es/home 322
// internal function; can't be linked to
static double semiperimeter(double a, double b, double c) {
return (a + b + c)/2.0;
}
// exported to client
double area(double a, double b, double c) {
const double s = semiperimeter(a, b, c);
return sqrt(s*(s-a)*(s-b)*(s-c));
}
2. Declara que una variable tiene una duración de almacenamiento estática (a menos que sea
thread_local ). Las variables del ámbito del espacio de nombres son implícitamente
estáticas. Una variable local estática se inicializa solo una vez, el primer control de tiempo
pasa por su definición y no se destruye cada vez que se sale de su alcance.
void f() {
static int count = 0;
std::cout << "f has been called " << ++count << " times so far\n";
}
struct S {
static S* create() {
return new S;
}
};
int main() {
S* s = S::create();
}
Tenga en cuenta que en el caso de un miembro de datos estáticos de una clase, tanto 2 como 3
se aplican simultáneamente: la palabra clave static convierte al miembro en un miembro de datos
estáticos y lo convierte en una variable con una duración de almacenamiento estática.
auto
C ++ 03
Declara una variable que tiene duración de almacenamiento automático. Es redundante, ya que la
duración del almacenamiento automático ya es la predeterminada en el ámbito del bloque, y el
especificador automático no está permitido en el ámbito del espacio de nombres.
void f() {
auto int x; // equivalent to: int x;
auto y; // illegal in C++; legal in C89
}
auto int z; // illegal: namespace-scope variable cannot be automatic
https://riptutorial.com/es/home 323
externo
1. Se puede utilizar para declarar una variable sin definirla. Por lo general, esto se usa en un
archivo de encabezado para una variable que se definirá en un archivo de implementación
separado.
// global scope
int x; // definition; x will be default-initialized
extern int y; // declaration; y is defined elsewhere, most likely another TU
extern int z = 42; // definition; "extern" has no effect here (compiler may warn)
2. constexpr un enlace externo a una variable en el ámbito del espacio de nombres, incluso si
const o constexpr hubieran provocado que tuviera un enlace interno.
// global scope
const int w = 42; // internal linkage in C++; external linkage in C
static const int x = 42; // internal linkage in both C++ and C
extern const int y = 42; // external linkage in both C++ and C
namespace {
extern const int z = 42; // however, this has internal linkage since
// it's in an unnamed namespace
}
3. Redecierra una variable en el ámbito del bloque si se declaró previamente con un enlace.
De lo contrario, declara una nueva variable con vinculación, que es un miembro del espacio
de nombres envolvente más cercano.
// global scope
namespace {
int x = 1;
struct C {
int x = 2;
void f() {
extern int x; // redeclares namespace-scope x
std::cout << x << '\n'; // therefore, this prints 1, not 2
}
};
}
void g() {
extern int y; // y has external linkage; refers to global y defined elsewhere
}
Una función también puede ser declarada extern , pero esto no tiene efecto. Generalmente se usa
como una sugerencia para el lector de que una función declarada aquí se define en otra unidad
de traducción. Por ejemplo:
https://riptutorial.com/es/home 324
En el código anterior, si f se cambiara a extern y g a no extern , no afectaría en absoluto la
corrección o la semántica del programa, pero probablemente confundiría al lector del código.
https://riptutorial.com/es/home 325
Capítulo 48: Estructuras de datos en C ++
Examples
Implementación de listas enlazadas en C ++
class listNode
{
public:
int data;
listNode *next;
listNode(int val):data(val),next(NULL){}
};
class List
{
public:
listNode *head;
List():head(NULL){}
void insertAtBegin(int val);
void insertAtEnd(int val);
void insertAtPos(int val);
void remove(int val);
void print();
~List();
};
https://riptutorial.com/es/home 326
{
ptr=ptr->next;
}
ptr->next=newnode;
}
Imprimir la lista
https://riptutorial.com/es/home 327
{
listNode *ptr=this->head;
while(ptr!=NULL)
{
cout<<ptr->data<<" " ;
ptr=ptr->next;
}
cout<<endl;
}
List::~List()
{
listNode *ptr=this->head,*next=NULL;
while(ptr!=NULL)
{
next=ptr->next;
delete(ptr);
ptr=next;
}
}
https://riptutorial.com/es/home 328
Capítulo 49: Estructuras de sincronización de
hilos.
Introducción
Trabajar con hilos puede necesitar algunas técnicas de sincronización si los hilos interactúan. En
este tema, puede encontrar las diferentes estructuras que proporciona la biblioteca estándar para
resolver estos problemas.
Examples
std :: shared_lock
Un shared_lock puede usarse junto con un bloqueo único para permitir múltiples lectores y
escritores exclusivos.
#include <unordered_map>
#include <mutex>
#include <shared_mutex>
#include <thread>
#include <string>
#include <iostream>
class PhoneBook {
public:
string getPhoneNo( const std::string & name )
{
shared_lock<shared_timed_mutex> r(_protect);
auto it = _phonebook.find( name );
if ( it == _phonebook.end() )
return (*it).second;
return "";
}
void addPhoneNo ( const std::string & name, const std::string & phone )
{
unique_lock<shared_timed_mutex> w(_protect);
_phonebook[name] = phone;
}
shared_timed_mutex _protect;
unordered_map<string,string> _phonebook;
};
https://riptutorial.com/es/home 329
#include <mutex>
#include <iostream>
std::once_flag flag;
void do_something(){
std::call_once(flag, [](){std::cout << "Happens once" << std::endl;});
A menudo desea bloquear todo el objeto mientras realiza varias operaciones en él. Por ejemplo,
si necesita examinar o modificar el objeto utilizando iteradores . Siempre que necesite llamar a
múltiples funciones miembro, generalmente es más eficiente bloquear todo el objeto en lugar de
funciones individuales.
Por ejemplo:
class text_buffer
{
// for readability/maintainability
using mutex_type = std::shared_timed_mutex;
using reading_lock = std::shared_lock<mutex_type>;
using updates_lock = std::unique_lock<mutex_type>;
public:
// This returns a scoped lock that can be shared by multiple
// readers at the same time while excluding any writers
[[nodiscard]]
reading_lock lock_for_reading() const { return reading_lock(mtx); }
private:
char buf[1024];
mutable mutex_type mtx; // mutable allows const objects to be locked
};
Al calcular una suma de comprobación, el objeto está bloqueado para la lectura, lo que permite
que otros subprocesos que desean leer del objeto al mismo tiempo lo hagan.
https://riptutorial.com/es/home 330
std::size_t checksum(text_buffer const& buf)
{
std::size_t sum = 0xA44944A4;
for(auto c: buf)
sum = (sum << 8) | (((unsigned char) ((sum & 0xFF000000) >> 24)) ^ c);
return sum;
}
Al borrar el objeto, se actualizan sus datos internos, por lo que se debe hacer utilizando un
bloqueo exclusivo.
Al obtener más de un bloqueo, se debe tener cuidado de adquirir siempre los bloqueos en el
mismo orden para todos los subprocesos.
nota: esto se hace mejor usando std :: deferred :: lock y llamando a std :: lock
std::cv_status como estado de retorno para una variable de condición tiene dos códigos de
retorno posibles:
https://riptutorial.com/es/home 331
Capítulo 50: Excepciones
Examples
Atrapando excepciones
#include <iostream>
#include <string>
#include <stdexcept>
int main() {
std::string str("foo");
try {
str.at(10); // access element, may throw std::out_of_range
} catch (const std::out_of_range& e) {
// what() is inherited from std::exception and contains an explanatory message
std::cout << e.what();
}
}
Se pueden usar múltiples cláusulas catch para manejar múltiples tipos de excepciones. Si hay
varias cláusulas catch , el mecanismo de manejo de excepciones intenta hacerlas coincidir en el
orden en que aparecen en el código:
std::string str("foo");
try {
str.reserve(2); // reserve extra capacity, may throw std::length_error
str.at(10); // access element, may throw std::out_of_range
} catch (const std::length_error& e) {
std::cout << e.what();
} catch (const std::out_of_range& e) {
std::cout << e.what();
}
Las clases de excepción que se derivan de una clase base común se pueden capturar con una
única cláusula catch para la clase base común. El ejemplo anterior puede reemplazar las dos
cláusulas catch para std::length_error y std::out_of_range con una sola cláusula para
std:exception :
std::string str("foo");
try {
str.reserve(2); // reserve extra capacity, may throw std::length_error
str.at(10); // access element, may throw std::out_of_range
} catch (const std::exception& e) {
https://riptutorial.com/es/home 332
std::cout << e.what();
}
Debido a que las cláusulas de catch se prueban en orden, asegúrese de escribir primero más
cláusulas de captura específicas, de lo contrario, es posible que nunca se llame a su código de
control de excepciones:
try {
/* Code throwing exceptions omitted. */
} catch (const std::exception& e) {
/* Handle all exceptions of type std::exception. */
} catch (const std::runtime_error& e) {
/* This block of code will never execute, because std::runtime_error inherits
from std::exception, and all exceptions of type std::exception were already
caught by the previous catch clause. */
}
try {
throw 10;
} catch (...) {
std::cout << "caught an exception";
}
A veces, desea hacer algo con la excepción que captura (como escribir en el registro o imprimir
una advertencia) y dejar que suba hasta el alcance superior para ser manejado. Para hacerlo,
puede volver a lanzar cualquier excepción que detecte:
try {
... // some code here
} catch (const SomeException& e) {
std::cout << "caught an exception";
throw;
}
C ++ 11
#include <iostream>
#include <string>
#include <exception>
#include <stdexcept>
https://riptutorial.com/es/home 333
if (eptr) {
std::rethrow_exception(eptr);
}
} catch(const std::exception& e) {
std::cout << "Caught exception \"" << e.what() << "\"\n";
}
}
int main()
{
std::exception_ptr eptr;
try {
std::string().at(1); // this generates an std::out_of_range
} catch(...) {
eptr = std::current_exception(); // capture
}
handle_eptr(eptr);
} // destructor for std::out_of_range called here, when the eptr is destructed
struct A : public B
{
A() try : B(), foo(1), bar(2)
{
// constructor body
}
catch (...)
{
// exceptions from the initializer list and constructor are caught here
// if no exception is thrown here
// then the caught exception is re-thrown.
}
private:
Foo foo;
Bar bar;
};
void function_with_try_block()
try
{
// try block body
}
catch (...)
{
// catch block body
}
Que es equivalente a
void function_with_try_block()
https://riptutorial.com/es/home 334
{
try
{
// try block body
}
catch (...)
{
// catch block body
}
}
Se permite que la función main tenga un bloque de prueba de función como cualquier otra función,
pero el bloque de prueba de la función main no detectará las excepciones que se producen
durante la construcción de una variable estática no local o la destrucción de cualquier variable
estática. En su lugar, se llama std::terminate .
struct A
{
~A() noexcept(false) try
{
// destructor body
}
catch (...)
{
// exceptions of destructor body are caught here
// if no exception is thrown here
// then the caught exception is re-thrown.
}
};
Tenga en cuenta que, aunque esto es posible, hay que tener mucho cuidado al lanzar desde el
destructor, ya que si un destructor llamado durante el desenrollado de la pila lanza una excepción,
se llama a std::terminate .
En general, se considera una buena práctica lanzar por valor (en lugar de por puntero), pero
capturar por (const) referencia.
try {
// throw new std::runtime_error("Error!"); // Don't do this!
// This creates an exception object
// on the heap and would require you to catch the
// pointer and manage the memory yourself. This can
// cause memory leaks!
throw std::runtime_error("Error!");
} catch (const std::runtime_error& e) {
https://riptutorial.com/es/home 335
std::cout << e.what() << std::endl;
}
Una razón por la cual la captura por referencia es una buena práctica es que elimina la necesidad
de reconstruir el objeto cuando se pasa al bloque catch (o cuando se propaga a otros bloques
catch). La captura por referencia también permite que las excepciones se manejen de forma
polimórfica y evita el corte de objetos. Sin embargo, si está volviendo a generar una excepción
(como throw e; vea el ejemplo a continuación), todavía puede obtener el corte de objetos porque
la throw e; La declaración hace una copia de la excepción a medida que se declara el tipo:
#include <iostream>
struct BaseException {
virtual const char* what() const { return "BaseException"; }
};
Si está seguro de que no va a hacer nada para cambiar la excepción (como agregar información o
modificar el mensaje), la captura por referencia constante permite al compilador realizar
optimizaciones y mejorar el rendimiento. Pero esto todavía puede causar el empalme de objetos
(como se ve en el ejemplo anterior).
La excepción jerarquizada
C ++ 11
https://riptutorial.com/es/home 336
Durante el manejo de excepciones, hay un caso de uso común cuando detecta una excepción
genérica de una función de bajo nivel (como un error del sistema de archivos o un error de
transferencia de datos) y lanza una excepción de alto nivel más específica que indica que alguna
operación de alto nivel podría no se realiza (como no poder publicar una foto en la Web). Esto
permite que el manejo de excepciones reaccione a problemas específicos con operaciones de
alto nivel y también permite, al tener solo un mensaje de error, al programador encontrar un lugar
en la aplicación donde se produjo una excepción. La desventaja de esta solución es que la
excepción callstack se trunca y la excepción original se pierde. Esto obliga a los desarrolladores a
incluir manualmente el texto de la excepción original en uno recién creado.
Las excepciones anidadas intentan resolver el problema adjuntando la excepción de bajo nivel,
que describe la causa, a una excepción de alto nivel, que describe lo que significa en este caso
particular.
#include <stdexcept>
#include <exception>
#include <string>
#include <fstream>
#include <iostream>
struct MyException
{
MyException(const std::string& message) : message(message) {}
std::string message;
};
https://riptutorial.com/es/home 337
//Empty // End recursion
}
}
// runs the sample function above and prints the caught exception
int main()
{
try {
run();
} catch(...) {
print_current_exception_with_nested();
}
}
Salida posible:
std :: uncaught_exceptions
c ++ 17
#include <exception>
#include <string>
#include <iostream>
https://riptutorial.com/es/home 338
// Apply change on destruction:
// Rollback in case of exception (failure)
// Else Commit (success)
class Transaction
{
public:
Transaction(const std::string& s) : message(s) {}
Transaction(const Transaction&) = delete;
Transaction& operator =(const Transaction&) = delete;
void Commit() { std::cout << message << ": Commit\n"; }
void RollBack() noexcept(true) { std::cout << message << ": Rollback\n"; }
// ...
~Transaction() {
if (uncaughtExceptionCount == std::uncaught_exceptions()) {
Commit(); // May throw.
} else { // current stack unwinding
RollBack();
}
}
private:
std::string message;
int uncaughtExceptionCount = std::uncaught_exceptions();
};
class Foo
{
public:
~Foo() {
try {
Transaction transaction("In ~Foo"); // Commit,
// even if there is an uncaught exception
//...
} catch (const std::exception& e) {
std::cerr << "exception/~Foo:" << e.what() << std::endl;
}
}
};
int main()
{
try {
Transaction transaction("In main"); // RollBack
Foo foo; // ~Foo commit its transaction.
//...
throw std::runtime_error("Error");
} catch (const std::exception& e) {
std::cerr << "exception/main:" << e.what() << std::endl;
}
}
Salida:
In ~Foo: Commit
In main: Rollback
exception/main:Error
https://riptutorial.com/es/home 339
Excepción personalizada
No debe lanzar valores brutos como excepciones, en su lugar, utilice una de las clases de
excepción estándar o cree las suyas propias.
Tener su propia clase de excepción heredada de std::exception es una buena manera de hacerlo.
Aquí hay una clase de excepción personalizada que hereda directamente de std::exception :
#include <exception>
protected:
public:
/** Destructor.
* Virtual to allow for subclassing.
*/
virtual ~Except() throw () {}
https://riptutorial.com/es/home 340
};
try {
throw(Except("Couldn't do what you were expecting", -12, -34));
} catch (const Except& e) {
std::cout<<e.what()
<<"\nError number: "<<e.getErrorNumber()
<<"\nError offset: "<<e.getErrorOffset();
}
Como no solo está lanzando un mensaje de error simple, sino también algunos otros valores que
representan el error exactamente, su manejo de errores se vuelve mucho más eficiente y
significativo.
Hay una clase de excepción que te permite manejar bien los mensajes de error:
std::runtime_error
#include <stdexcept>
protected:
public:
/** Destructor.
* Virtual to allow for subclassing.
*/
virtual ~Except() throw () {}
https://riptutorial.com/es/home 341
}
};
https://riptutorial.com/es/home 342
Capítulo 51: Expresiones Fold
Observaciones
Fold Expressions son compatibles con los siguientes operadores
+ - * / % \ˆ Y | << >>
Al plegar una secuencia vacía, una expresión de plegado está mal formada, excepto por los
siguientes tres operadores:
&& cierto
|| falso
, vacío()
Examples
Pliegues Unarios
Los pliegues únicos se utilizan para plegar paquetes de parámetros sobre un operador específico.
Hay 2 tipos de pliegues únicos:
template<typename... Ts>
int sum(Ts... args)
{
return (... + args); //Unary left fold
//return (args + ...); //Unary right fold
https://riptutorial.com/es/home 343
// The two are equivalent if the operator is associative.
// For +, ((1+2)+3) (left fold) == (1+(2+3)) (right fold)
// For -, ((1-2)-3) (left fold) != (1-(2-3)) (right fold)
}
Pliegues binarios
Los pliegues binarios son básicamente pliegues únicos , con un argumento adicional.
template<typename... Ts>
int removeFrom(int num, Ts... args)
{
return (num - ... - args); //Binary left fold
// Note that a binary right fold cannot be used
// due to the lack of associativity of operator-
}
Es una operación común la necesidad de realizar una función particular sobre cada elemento en
un paquete de parámetros. Con C ++ 11, lo mejor que podemos hacer es:
Pero con una expresión de doblez, lo anterior se simplifica muy bien para:
https://riptutorial.com/es/home 344
}
https://riptutorial.com/es/home 345
Capítulo 52: Expresiones regulares
Introducción
Las expresiones regulares (a veces llamadas expresiones regulares o expresiones regulares )
son una sintaxis textual que representa los patrones que se pueden hacer coincidir en las
cadenas operadas.
Sintaxis
• regex_match // Devuelve si la secuencia de caracteres completa coincidió con la expresión
regular, opcionalmente capturando en un objeto coincidente
• regex_search // Devuelve si una parte de la secuencia de caracteres coincidió con la
expresión regular, opcionalmente capturando en un objeto coincidente
• regex_replace // Devuelve la secuencia de caracteres de entrada modificada por una
expresión regular a través de una cadena de formato de reemplazo
• regex_token_iterator // Inicializado con una secuencia de caracteres definida por iteradores,
una lista de índices de captura para iterar, y una expresión regular. La desreferenciación
devuelve la coincidencia actualmente indexada de la expresión regular. El incremento se
mueve al siguiente índice de captura o, si está actualmente en el último índice, restablece el
índice y dificulta la próxima aparición de una coincidencia de expresiones regulares en la
secuencia de caracteres
• regex_iterator // Inicializado con una secuencia de caracteres definida por iteradores y una
expresión regular. La anulación de referencia devuelve la parte de la secuencia de
caracteres con la que coinciden actualmente todas las expresiones regulares.
Incrementando encuentra la siguiente aparición de una coincidencia de expresiones
regulares en la secuencia de caracteres
Parámetros
Firma Descripción
https://riptutorial.com/es/home 346
Firma Descripción
Examples
Ejemplos básicos de regex_match y regex_search
const auto input = "Some people, when confronted with a problem, think \"I know, I'll use
regular expressions.\""s;
smatch sm;
// If input ends in a quotation that contains a word that begins with "reg" and another word
begining with "ex" then capture the preceeding portion of input
if (regex_match(input, sm, regex("(.*)\".*\\breg.*\\bex.*\"\\s*$"))) {
const auto capture = sm[1].str();
cout << '\t' << capture << endl; // Outputs: "\tSome people, when confronted with a
problem, think\n"
cout << '\t' << count << (count > 1 ? " problems\n" : " problem\n"); // Outputs: "\t1
problem\n"
cout << "Now they have " << count + 1 << " problems.\n"; // Ouputs: "Now they have 2
problems\n"
}
}
Ejemplo vivo
Ejemplo de regex_replace
Este código toma varios estilos de llaves y los convierte en One True Brace Style :
https://riptutorial.com/es/home 347
(allman)\n{\n\tfoo();\n}\nif (horstmann)\n{\tfoo();\n}\nif (pico)\n{\tfoo(); }\nif
(whitesmiths)\n\t{\n\tfoo();\n\t}\n"s;
Ejemplo vivo
regex_token_iterator Ejemplo
Ejemplo vivo
Un problema notable con los iteradores de expresiones regulares es que el argumento de regex
debe ser un valor de l. Un valor R no funcionará .
Ejemplo de regex_iterator
enum TOKENS {
NUMBER,
ADDITION,
SUBTRACTION,
MULTIPLICATION,
DIVISION,
EQUALITY,
OPEN_PARENTHESIS,
CLOSE_PARENTHESIS
};
Podemos tokenizar esta cadena: const auto input = "42/2 + -8\t=\n(2 + 2) * 2 * 2 -3"s con un
regex_iterator como este:
vector<TOKENS> tokens;
const regex re{ "\\s*(\\(?)\\s*(-?\\s*\\d+)\\s*(\\)?)\\s*(?:(\\+)|(-)|(\\*)|(/)|(=))" };
https://riptutorial.com/es/home 348
for_each(sregex_iterator(cbegin(input), cend(input), re), sregex_iterator(), [&](const auto&
i) {
if(i[1].length() > 0) {
tokens.push_back(OPEN_PARENTHESIS);
}
if(i[3].length() > 0) {
tokens.push_back(CLOSE_PARENTHESIS);
}
match_results<string::const_reverse_iterator> sm;
Ejemplo vivo
Un problema notable con los iteradores de expresiones regulares es que el argumento de regex
debe ser un valor L, un valor R no funcionará: Visual Studio regex_iterator Bug?
split("Some string\t with whitespace ", "\\s+"); // "Some", "string", "with", "whitespace"
Cuantificadores
Digamos que nos dan const string input como un número de teléfono para validar. Podríamos
comenzar requiriendo una entrada numérica con cero o más cuantificadores :
regex_match(input, regex("\\d*")) o uno o más cuantificadores : regex_match(input,
regex("\\d+")) Pero ambos se quedan cortos si la input contiene una cadena numérica no válida
como: "123" Usemos uno o más cuantificadores para asegurarnos de que obtengamos al
menos 7 dígitos:
https://riptutorial.com/es/home 349
regex_match(input, regex("\\d{7,}"))
Esto garantizará que obtendremos al menos un número de teléfono de dígitos, pero la input
también podría contener una cadena numérica que sea demasiado larga como: "123456789012".
Así que vamos con un cuantificador entre n y m para que la input tenga al menos 7 dígitos pero
no más de 11:
regex_match(input, regex("\\d{7,11}"));
Esto nos acerca, pero todavía se aceptan cadenas numéricas ilegales que están en el rango de
[7, 11], como: "123456789" Así que hagamos que el código de país sea opcional con un
cuantificador perezoso :
regex_match(input, regex("\\d?\\d{7,10}"))
Es importante tener en cuenta que el cuantificador perezoso hace coincidir la menor cantidad
de caracteres posible , por lo que la única manera de que este carácter coincida es si ya hay 10
caracteres que se han comparado con \d{7,10} . (Para hacer coincidir el primer carácter con
avidez, habríamos tenido que hacer: \d{0,1} .) El cuantificador perezoso se puede agregar a
cualquier otro cuantificador.
Ahora, ¿cómo haríamos el código de área opcional y solo aceptaríamos un código de país si el
código de área estaba presente?
regex_match(input, regex("(?:\\d{3,4})?\\d{7}"))
En esta expresión regular, el \d{7} requiere 7 dígitos. Estos 7 dígitos están precedidos
opcionalmente por 3 o 4 dígitos.
En conclusión del tema del cuantificador, me gustaría mencionar el otro cuantificador adjunto que
puede usar, el cuantificador posesivo . El cuantificador perezoso o el cuantificador posesivo
se pueden agregar a cualquier cuantificador. La única función del cuantificador posesivo es
ayudar al motor de expresiones regulares diciéndole, tome estos caracteres con avidez y nunca
los abandone, incluso si causa que la expresión regular falle . Esto, por ejemplo, no tiene mucho
sentido: regex_match(input, regex("\\d{3,4}+\\d{7})) porque una input como:" 1234567890 "no
coincidiría con \d{3,4}+ siempre coincidirá con 4 caracteres, incluso si la coincidencia con 3
hubiera permitido que la expresión regular sea exitosa.
El cuantificador posesivo se utiliza mejor cuando el token cuantificado limita el número de
caracteres comparables . Por ejemplo:
regex_match(input, regex("(?:.*\\d{3,4}+){3}"))
https://riptutorial.com/es/home 350
Se puede usar para hacer coincidir si la input contenía alguno de los siguientes:
Pero cuando esta expresión regular realmente brilla es cuando la input contiene una entrada
ilegal :
12345 - 67890
Sin el cuantificador posesivo, el motor de expresiones regulares debe regresar y probar cada
combinación de .* Y 3 o 4 caracteres para ver si puede encontrar una combinación compatible.
Con el cuantificador posesiva las expresiones regulares comienza donde el cuantificador 2º
posesivo fue apagado, el carácter '0', y el motor de expresiones regulares trata de ajustar el .*
Para permitir \d{3,4} para que coincida; cuando no se puede, la expresión regular simplemente
falla, no se realiza un seguimiento de retroceso para ver si anteriormente .* ajuste podría haber
permitido una coincidencia.
Anclas
do {
cout << sm[1] << endl;
input = sm.suffix().str();
} while(regex_search(input, sm, regex{ "(?:^\\W|\\b\\W)([+-]?\\d+)" }));
}
Ejemplo vivo
https://riptutorial.com/es/home 351
Capítulo 53: Fecha y hora usando
encabezamiento
Examples
Tiempo de medición utilizando
El system_clock se puede usar para medir el tiempo transcurrido durante alguna parte de la
ejecución de un programa.
c ++ 11
#include <iostream>
#include <chrono>
#include <thread>
int main() {
auto start = std::chrono::system_clock::now(); // This and "end"'s type is
std::chrono::time_point
{ // The code to test
std::this_thread::sleep_for(std::chrono::seconds(2));
}
auto end = std::chrono::system_clock::now();
En este ejemplo, se usó sleep_for para hacer que el subproceso activo esté inactivo durante un
período de tiempo medido en std::chrono::seconds , pero el código entre llaves puede ser
cualquier llamada de función que tarde algún tiempo en ejecutarse.
Este ejemplo muestra cómo encontrar el número de días entre dos fechas. La fecha se especifica
por año / mes / día del mes y, además, hora / minuto / segundo.
#include <iostream>
#include <string>
#include <chrono>
#include <ctime>
/***
* Creates a std::tm structure from raw date.
*
* \param year (must be 1900 or greater)
* \param month months since January – [1, 12]
* \param day day of the month – [1, 31]
https://riptutorial.com/es/home 352
* \param minutes minutes after the hour – [0, 59]
* \param seconds seconds after the minute – [0, 61](until C++11) / [0, 60] (since C++11)
*
* Based on http://en.cppreference.com/w/cpp/chrono/c/tm
*/
std::tm CreateTmStruct(int year, int month, int day, int hour, int minutes, int seconds) {
struct tm tm_ret = {0};
tm_ret.tm_sec = seconds;
tm_ret.tm_min = minutes;
tm_ret.tm_hour = hour;
tm_ret.tm_mday = day;
tm_ret.tm_mon = month - 1;
tm_ret.tm_year = year - 1900;
return tm_ret;
}
return diff_in_days.count();
}
int main()
{
for ( int year = 2000; year <= 2016; ++year )
std::cout << "There are " << get_days_in_year(year) << " days in " << year << "\n";
}
https://riptutorial.com/es/home 353
Capítulo 54: Función de C ++ "llamada por
valor" vs. "llamada por referencia"
Introducción
El alcance de esta sección es explicar las diferencias en la teoría y la implementación de lo que
sucede con los parámetros de una función al llamar.
En detalle, los parámetros se pueden ver como variables antes de la llamada a la función y dentro
de la función, donde el comportamiento visible y la accesibilidad a estas variables difiere con el
método utilizado para entregarlas.
Además, este tema también explica la reutilización de las variables y sus valores respectivos
después de la llamada a la función.
Examples
Llamar por valor
Al llamar a una función hay nuevos elementos creados en la pila de programas. Estos incluyen
alguna información sobre la función y también el espacio (ubicaciones de memoria) para los
parámetros y el valor de retorno.
Cuando se entrega un parámetro a una función, el valor de la variable utilizada (o literal) se copia
en la ubicación de memoria del parámetro de la función. Esto implica que ahora hay dos
ubicaciones de memoria con el mismo valor. Dentro de la función solo trabajamos en la ubicación
de memoria de parámetros.
int main(void) {
int a = 0;
int b = 1; //outer_b
https://riptutorial.com/es/home 354
int c;
c = func(a,b);
//the return value is copied to c
En este código creamos variables dentro de la función principal. Estos obtienen valores
asignados. Al llamar a las funciones, se crean dos nuevas variables: f e inner_b donde b comparte
el nombre con la variable externa y no comparte la ubicación de la memoria. El comportamiento
de a<->f y b<->b es idéntico.
El siguiente gráfico simboliza lo que está sucediendo en la pila y por qué no hay cambios en
varibale b . El gráfico no es completamente exacto pero enfatiza el ejemplo.
Se llama "llamada por valor" porque no entregamos las variables, sino solo los valores de estas
variables.
Lea Función de C ++ "llamada por valor" vs. "llamada por referencia" en línea:
https://riptutorial.com/es/cplusplus/topic/10669/funcion-de-c-plusplus--llamada-por-valor--vs---
llamada-por-referencia-
https://riptutorial.com/es/home 355
Capítulo 55: Función de sobrecarga de
plantillas
Observaciones
• Una función normal nunca está relacionada con una plantilla de función, a pesar del mismo
nombre, del mismo tipo.
• Una llamada de función normal y una llamada de plantilla de función generada son
diferentes incluso si comparten el mismo nombre, el mismo tipo de retorno y la misma lista
de argumentos
Examples
¿Qué es una sobrecarga de plantilla de función válida?
Una plantilla de función se puede sobrecargar bajo las reglas para la sobrecarga de funciones
que no son de plantilla (mismo nombre, pero diferentes tipos de parámetros) y además, la
sobrecarga es válida si
Para una función normal, la comparación de dos tipos de parámetros es fácil para el compilador,
ya que tiene toda la información. Pero un tipo dentro de una plantilla puede no estar determinado
todavía. Por lo tanto, la regla para cuando dos tipos de parámetros son iguales es aproximativa
aquí y dice que los tipos y valores no dependientes deben coincidir y que la ortografía de los tipos
y expresiones dependientes debe ser la misma (más precisamente, deben ajustarse a la
denominadas reglas de ODR), excepto que los parámetros de la plantilla pueden ser
renombrados. Sin embargo, si bajo tales ortografías diferentes, dos valores dentro de los tipos se
consideran diferentes, pero siempre se crea una instancia de los mismos valores, la sobrecarga
no es válida, pero no se requiere ningún diagnóstico del compilador.
template<typename T>
void f(T*) { }
template<typename T>
void f(T) { }
Esta es una sobrecarga válida, ya que "T" y "T *" son ortografías diferentes. Pero lo siguiente no
es válido, sin diagnóstico requerido
template<typename T>
void f(T (*x)[sizeof(T) + sizeof(T)]) { }
https://riptutorial.com/es/home 356
template<typename T>
void f(T (*x)[2 * sizeof(T)]) { }
https://riptutorial.com/es/home 357
Capítulo 56: Funciones de miembro de clase
constante
Observaciones
Lo que realmente significa "funciones miembro const" de una clase significa. La definición simple
parece ser que una función miembro const no puede cambiar el objeto. Pero lo que significa 'no
puede cambiar' realmente significa aquí. Simplemente significa que no puede hacer una
asignación para miembros de datos de clase.
Sin embargo, puede realizar otras operaciones indirectas como insertar una entrada en un mapa
como se muestra en el ejemplo. Permitir que esto parezca que esta función const es modificar el
objeto (sí, lo hace en un sentido), pero está permitido.
Entonces, el significado real es que una función miembro const no puede hacer una asignación
para las variables de datos de clase. Pero puede hacer otras cosas como las explicadas en el
ejemplo.
Examples
función miembro constante
#include <iostream>
#include <map>
#include <string>
class A {
public:
map<string, string> * mapOfStrings;
public:
A() {
mapOfStrings = new map<string, string>();
}
void insertEntry(string const & key, string const & value) const {
(*mapOfStrings)[key] = value; // This works? Yes it does.
delete mapOfStrings; // This also works
mapOfStrings = new map<string, string>(); // This * does * not work
}
void refresh() {
delete mapOfStrings;
mapOfStrings = new map<string, string>(); // Works as refresh is non const function
}
https://riptutorial.com/es/home 358
};
A var;
var.insertEntry("abc", "abcValue");
var.getEntry("abc");
getchar();
return 0;
}
https://riptutorial.com/es/home 359
Capítulo 57: Funciones de miembro virtual
Sintaxis
• vacío virtual f ();
• vacío virtual g () = 0;
• // C ++ 11 o posterior:
Observaciones
• Solo las funciones miembro no estáticas, sin plantilla pueden ser virtual .
• Si está utilizando C ++ 11 o posterior, se recomienda usar la función de override al anular
una función de miembro virtual de una clase base.
• Las clases base polimórficas a menudo tienen destructores virtuales para permitir que un
objeto derivado se elimine a través de un puntero a la clase base . Si el destructor no fuera
virtual, tal operación conduce a un comportamiento indefinido [expr.delete] §5.3.5 / 3 .
Examples
Usando override con virtual en C ++ 11 y versiones posteriores
No hay run time significado de run time de este especificador, ya que está pensado
principalmente como una indicación para compiladores
El siguiente ejemplo demostrará el cambio en el comportamiento con nuestro uso sin anular.
Sin override :
#include <iostream>
struct X {
virtual void f() { std::cout << "X::f()\n"; }
};
https://riptutorial.com/es/home 360
struct Y : X {
// Y::f() will not override X::f() because it has a different signature,
// but the compiler will accept the code (and silently ignore Y::f()).
virtual void f(int a) { std::cout << a << "\n"; }
};
Con override :
#include <iostream>
struct X {
virtual void f() { std::cout << "X::f()\n"; }
};
struct Y : X {
// The compiler will alert you to the fact that Y::f() does not
// actually override anything.
virtual void f(int a) override { std::cout << a << "\n"; }
};
Tenga en cuenta que la override no es una palabra clave, sino un identificador especial que solo
puede aparecer en las firmas de funciones. En todos los demás contextos, la override todavía se
puede usar como un identificador:
void foo() {
int override = 1; // OK.
int virtual = 2; // Compilation error: keywords can't be used as identifiers.
}
#include <iostream>
struct X {
virtual void f() { std::cout << "X::f()\n"; }
};
struct Y : X {
// Specifying virtual again here is optional
// because it can be inferred from X::f().
virtual void f() { std::cout << "Y::f()\n"; }
};
void call(X& a) {
a.f();
}
int main() {
X x;
Y y;
call(x); // outputs "X::f()"
call(y); // outputs "Y::f()"
}
https://riptutorial.com/es/home 361
Sin funciones de miembro virtual:
#include <iostream>
struct X {
void f() { std::cout << "X::f()\n"; }
};
struct Y : X {
void f() { std::cout << "Y::f()\n"; }
};
void call(X& a) {
a.f();
}
int main() {
X x;
Y y;
call(x); // outputs "X::f()"
call(y); // outputs "X::f()"
}
class Base {
public:
virtual void foo() {
std::cout << "Base::Foo\n";
}
};
El especificador final solo se puede utilizar con la función miembro "virtual" y no se puede aplicar
a funciones miembro no virtuales
Como final , también hay un especificador de llamada 'reemplazo' que evita el reemplazo de
funciones virtual en la clase derivada.
https://riptutorial.com/es/home 362
Los especificadores override y final pueden combinarse para tener el efecto deseado:
#include <iostream>
using namespace std;
class base {
public:
base() { f("base constructor"); }
~base() { f("base destructor"); }
};
int main() {
derived d;
}
Salida:
El razonamiento detrás de esto es que la clase derivada puede definir miembros adicionales que
aún no están inicializados (en el caso del constructor) o que ya están destruidos (en el caso del
destructor), y llamar a sus funciones miembro no sería seguro. Por lo tanto, durante la
construcción y destrucción de objetos C ++, el tipo dinámico de *this se considera que es la clase
del constructor o destructor y no una clase más derivada.
https://riptutorial.com/es/home 363
Ejemplo:
#include <iostream>
#include <memory>
int main() {
derived d(4);
}
También podemos especificar que una función virtual es simplemente virtual (abstracta),
agregando = 0 a la declaración. Las clases con una o más funciones virtuales puras se
consideran abstractas y no se pueden crear instancias; solo las clases derivadas que definen o
heredan definiciones para todas las funciones virtuales puras pueden ser instanciadas.
struct Abstract {
virtual void f() = 0;
};
struct Concrete {
void f() override {}
};
Abstract a; // Error.
Concrete c; // Good.
Incluso si una función se especifica como virtual pura, se le puede dar una implementación
predeterminada. A pesar de esto, la función todavía se considerará abstracta, y las clases
derivadas tendrán que definirla antes de que puedan ser instanciadas. En este caso, la versión de
la clase derivada de la función puede incluso llamar a la versión de la clase base.
struct DefaultAbstract {
virtual void f() = 0;
};
void DefaultAbstract::f() {}
https://riptutorial.com/es/home 364
struct WhyWouldWeDoThis : DefaultAbstract {
void f() override { DefaultAbstract::f(); }
};
Hay un par de razones por las que podríamos querer hacer esto:
• Si queremos crear una clase que no pueda ser instanciada, pero que no evite que sus
clases derivadas sean instanciadas, podemos declarar el destructor como simplemente
virtual. Al ser el destructor, debe definirse de todos modos, si queremos poder desasignar la
instancia. Y como es muy probable que el destructor ya sea virtual para evitar fugas de
memoria durante el uso polimórfico , no incurriremos en un impacto de rendimiento
innecesario al declarar otra función virtual . Esto puede ser útil al hacer interfaces.
struct Interface {
virtual ~Interface() = 0;
};
Interface::~Interface() = default;
class SharedBase {
State my_state;
std::unique_ptr<Helper> my_helper;
// ...
public:
virtual void config(const Context& cont) = 0;
// ...
};
/* virtual */ void SharedBase::config(const Context& cont) {
my_helper = new Helper(my_state, cont.relevant_field);
do_this();
and_that();
}
public:
void config(const Context& cont) override;
// ...
};
void OneImplementation::config(const Context& cont) /* override */ {
my_state = { cont.some_field, cont.another_field, i };
SharedBase::config(cont);
my_unique_setup();
};
https://riptutorial.com/es/home 365
// And so on, for other classes derived from SharedBase.
https://riptutorial.com/es/home 366
Capítulo 58: Funciones en linea
Introducción
Una función definida con el especificador en inline es una función en línea. Una función en línea
se puede definir de forma múltiple sin violar la Regla de una definición y, por lo tanto, se puede
definir en un encabezado con enlace externo. Declarar una función en línea sugiere al compilador
que la función debe estar en línea durante la generación del código, pero no proporciona una
garantía.
Sintaxis
• inline function_declaration
• inline function_definition
• class {function_definition};
Observaciones
Generalmente, si el código generado para una función es lo suficientemente pequeño, entonces
es un buen candidato para estar en línea. ¿Porque? Si una función es grande y está en línea en
un bucle, para todas las llamadas realizadas, el código de la función grande se duplicará, lo que
provocará la inflexión del tamaño binario generado. Pero, ¿qué tan pequeño es suficiente?
Si bien las funciones en línea parecen ser una excelente manera de evitar la sobrecarga de las
llamadas de funciones, se debe tener en cuenta que no todas las funciones que están marcadas
en inline están en línea. En otras palabras, cuando se dice en inline , es solo una sugerencia
para el compilador, no un pedido: el compilador no está obligado a incluir la función en línea, es
libre de ignorarla, la mayoría lo hace. Los compiladores modernos son mejores para hacer tales
optimizaciones que esta palabra clave es ahora un vestigio del pasado, cuando esta sugerencia
de función incorporada por el programador fue tomada en serio por los compiladores. Incluso las
funciones no marcadas en inline están integradas por el compilador cuando ve beneficio al
hacerlo.
https://riptutorial.com/es/home 367
Preguntas frecuentes
¿Cuándo debo escribir la palabra clave 'en línea' para una función / método en C ++?
Solo cuando desea que la función se defina en un encabezado. Más exactamente solo cuando la
definición de la función puede aparecer en varias unidades de compilación. Es una buena idea
definir funciones pequeñas (como en un trazador de líneas) en el archivo de encabezado, ya que
le da al compilador más información para trabajar mientras optimiza su código. También aumenta
el tiempo de compilación.
¿Cuándo no debo escribir la palabra clave 'en línea' para una función / método en C ++?
No agregue en inline cuando crea que su código se ejecutará más rápido si el compilador lo
alinea.
Generalmente, el compilador podrá hacer esto mejor que tú. Sin embargo, el compilador no tiene
la opción de código en línea si no tiene la definición de la función. En el código optimizado al
máximo, generalmente todos los métodos privados están en línea, ya sea que lo solicite o no.
Ver también
• ¿Cuándo debo escribir la palabra clave 'en línea' para una función / método?
Examples
Declaración de función en línea no miembro
// header (.hpp)
struct A
{
https://riptutorial.com/es/home 368
void i_am_inlined()
{
}
};
struct B
{
void i_am_NOT_inlined();
};
// source (.cpp)
void B::i_am_NOT_inlined()
{
}
int main()
{
int a = 1, b = 2;
int c = add(a, b);
}
En el código anterior, cuando la add está en línea, el código resultante se convertirá en algo como
esto
int main()
{
int a = 1, b = 2;
int c = a + b;
}
https://riptutorial.com/es/home 369
Capítulo 59: Funciones especiales para
miembros
Examples
Destructores virtuales y protegidos.
Una clase diseñada para ser heredada se llama clase base. Se debe tener cuidado con las
funciones especiales para miembros de dicha clase.
Una clase diseñada para ser utilizada polimórficamente en tiempo de ejecución (a través de un
puntero a la clase base) debe declarar el destructor virtual . Esto permite que las partes
derivadas del objeto se destruyan adecuadamente, incluso cuando el objeto se destruye a través
de un puntero a la clase base.
class Base {
public:
virtual ~Base() = default;
private:
// data members etc.
};
private:
// more data members
};
Si la clase no necesita ser polimórfica, pero aún necesita permitir que su interfaz sea heredada,
use un destructor protected no virtual.
class NonPolymorphicBase {
public:
// some methods
protected:
~NonPolymorphicBase() = default; // note: non-virtual
private:
// etc.
};
Tal clase nunca puede ser destruida a través de un puntero, evitando fugas silenciosas debido al
https://riptutorial.com/es/home 370
corte en rodajas.
Esta técnica se aplica especialmente a las clases diseñadas para ser clases base private . Una
clase de este tipo podría usarse para encapsular algunos detalles de implementación comunes, al
tiempo que proporciona métodos virtual como puntos de personalización. Este tipo de clase
nunca debe utilizarse polimórficamente, y un destructor protected ayuda a documentar este
requisito directamente en el código.
Finalmente, algunas clases pueden requerir que nunca se usen como clase base. En este caso,
la clase se puede marcar como final . Un destructor público no virtual normal está bien en este
caso.
private:
// etc.
};
Tenga en cuenta que declarar un destructor impide que el compilador genere constructores de
movimiento implícito y operadores de asignación de movimiento. Si declara un destructor,
recuerde agregar también las definiciones apropiadas para las operaciones de movimiento.
class Movable {
public:
virtual ~Movable() noexcept = default;
Copiar e intercambiar
Si está escribiendo una clase que administra recursos, debe implementar todas las funciones
especiales para miembros (consulte la Regla de tres / cinco / cero ). El enfoque más directo para
escribir el constructor de copia y el operador de asignación sería:
https://riptutorial.com/es/home 371
person(const person &other)
: name(new char[std::strlen(other.name) + 1])
, age(other.age)
{
std::strcpy(name, other.name);
}
return *this;
}
Pero este enfoque tiene algunos problemas. Falla la fuerte garantía excepción - si es new[] tiros,
que ya hemos aclarado los recursos propiedad de this y no se puede recuperar. Estamos
duplicando gran parte de la lógica de la construcción de copias en la asignación de copias. Y
tenemos que recordar la verificación de autoasignación, que generalmente solo agrega gastos
generales a la operación de copia, pero aún es crítica.
Para satisfacer la fuerte garantía de excepción y evitar la duplicación de código (doble con el
operador de asignación de movimiento subsiguiente), podemos utilizar el lenguaje de copia e
intercambio:
class person {
char* name;
int age;
public:
/* all the other functions ... */
swap(lhs.name, rhs.name);
swap(lhs.age, rhs.age);
}
person p1 = ...;
person p2 = ...;
p1 = p2;
Primero, copiamos y construimos rhs desde p2 (que no tuvimos que duplicar aquí). Si esa
operación se produce, no hacemos nada en el operator= y p1 permanece intacto. A continuación,
https://riptutorial.com/es/home 372
intercambiamos los miembros entre *this y rhs , y luego rhs queda fuera del alcance. Cuando
operator= , eso limpia de forma implícita los recursos originales de this (a través del destructor,
que no tuvimos que duplicar). La autoasignación también funciona: es menos eficiente con la
copia e intercambio (implica una asignación adicional y una desasignación), pero si ese es el
escenario improbable, no ralentizamos el caso de uso típico para tenerlo en cuenta.
C ++ 11
p1 = std::move(p2);
Aquí, movemos-construimos rhs desde p2 , y todo lo demás es igual de válido. Si una clase es
movible pero no puede copiarse, no es necesario eliminar la asignación de copia, ya que este
operador de asignación simplemente tendrá una forma incorrecta debido al constructor de copia
eliminado.
Constructor predeterminado
class C{
int i;
public:
// the default constructor definition
C()
: i(0){ // member initializer list -- initialize i to 0
// constructor function body -- can do more complex things here
}
};
class D{
int i;
int j;
public:
// also a default constructor (can be called with no parameters)
D( int i = 0, int j = 42 )
: i(i), j(j){
}
};
https://riptutorial.com/es/home 373
D d; // calls constructor of D with the provided default values for the parameters
class C{
std::string s; // note: members need to be default constructible themselves
};
Tener algún otro tipo de constructor es una de las condiciones de descalificación mencionadas
anteriormente:
class C{
int i;
public:
C( int i ) : i(i){}
};
c ++ 11
Para evitar la creación implícita del constructor predeterminado, una técnica común es declararla
como private (sin definición). La intención es provocar un error de compilación cuando alguien
intenta usar el constructor (esto puede resultar en un error de Acceso a privado o un error de
vinculador, según el compilador).
c ++ 11
En C ++ 11, un desarrollador también puede usar la palabra clave delete para evitar que el
compilador proporcione un constructor predeterminado.
class C{
int i;
public:
// default constructor is explicitly deleted
C() = delete;
};
Además, un desarrollador también puede ser explícito acerca de querer que el compilador
proporcione un constructor predeterminado.
https://riptutorial.com/es/home 374
class C{
int i;
public:
// does have automatically generated default constructor (same as implicit one)
C() = default;
C( int i ) : i(i){}
};
c ++ 14
class C1{ };
class C2{ public: C2(){} };
class C3{ public: C3(int){} };
c ++ 11
Incinerador de basuras
Un destructor es una función sin argumentos que se llama cuando un objeto definido por el
usuario está a punto de ser destruido. Se nombra después del tipo que destruye con un prefijo ~ .
class C{
int* is;
string s;
public:
C()
: is( new int[10] ){
}
https://riptutorial.com/es/home 375
~C_child(){} // child destructor
};
void f(){
C c1; // calls default constructor
C c2[2]; // calls default constructor for both elements
C* c3 = new C[2]; // calls default constructor for both array elements
C_child c_ch; // when destructed calls destructor of s_ch and of C base (and in turn s)
class C{
int i;
string s;
};
void f(){
C* c1 = new C;
delete c1; // C has a destructor
}
class C{
int m;
private:
~C(){} // not public destructor!
};
class C_container{
C c;
};
void f(){
C_container* c_cont = new C_container;
delete c_cont; // Compile ERROR: C has no accessible destructor
}
c ++ 11
class C{
int m;
public:
~C() = delete; // does NOT have implicit destructor
};
void f{
C c1;
https://riptutorial.com/es/home 376
} // Compile ERROR: C has no destructor
Además, un desarrollador también puede ser explícito acerca de querer que el compilador
proporcione un destructor predeterminado.
class C{
int m;
public:
~C() = default; // saying explicitly it does have implicit/empty destructor
};
void f(){
C c1;
} // C has a destructor -- c1 properly destroyed
c ++ 11
class C1{ };
class C2{ public: ~C2() = delete };
class C3 : public C2{ };
https://riptutorial.com/es/home 377
Capítulo 60: Funciones miembro no estáticas
Sintaxis
• // Llamando:
○ variable.member_function ();
○ variable_pointer-> miembro_función ();
• // Definición:
• // Prototipo:
○ clase nombre_clase {
○ virt-specifier ret_type member_function () cv-qualifiers virt-specifier-seq;
○ // virt-specifier: "virtual", si corresponde.
○ // calificadores cv: "const" y / o "volátil", si corresponde.
○ // virt-specifier-seq: "anular" y / o "final", si corresponde.
○ }
Observaciones
Una función miembro no static es una función miembro class / struct / union , que se llama en
una instancia particular y opera en dicha instancia. A diferencia de las funciones miembro static ,
no se puede llamar sin especificar una instancia.
Para obtener información sobre clases, estructuras y uniones, consulte el tema principal .
Examples
Funciones miembro no estáticas
Una class o struct puede tener funciones miembro así como variables miembro. Estas funciones
tienen una sintaxis mayoritariamente similar a las funciones independientes, y pueden definirse
dentro o fuera de la definición de clase; Si se define fuera de la definición de la clase, el nombre
de la función tiene como prefijo el nombre de la clase y el operador de alcance ( :: :).
class CL {
public:
void definedInside() {}
void definedOutside();
};
https://riptutorial.com/es/home 378
void CL::definedOutside() {}
Estas funciones se invocan en una instancia (o referencia a una instancia) de la clase con el
operador de punto ( . ), O un puntero a una instancia con el operador de flecha ( -> ), y cada
llamada está vinculada a la instancia de la función fue llamado en cuando se llama a una función
miembro en una instancia, tiene acceso a todos los campos de esa instancia (a través de this
puntero ), pero solo puede acceder a los campos de otras instancias si esas instancias se
suministran como parámetros.
struct ST {
ST(const std::string& ss = "Wolf", int ii = 359) : s(ss), i(ii) { }
private:
std::string s;
int i;
};
ST st1;
ST st2("Species", 8472);
Estas funciones pueden acceder a las variables miembro y / u otras funciones miembro,
independientemente de la variable o los modificadores de acceso de la función. También se
pueden escribir fuera de orden, accediendo a las variables de miembro y / o llamando a las
funciones de miembro declaradas antes de ellas, ya que se debe analizar toda la definición de la
clase antes de que el compilador pueda comenzar a compilar una clase.
class Access {
public:
Access(int i_ = 8088, int j_ = 8086, int k_ = 6502) : i(i_), j(j_), k(k_) {}
int i;
int get_k() const { return k; }
bool private_no_more() const { return i_be_private(); }
protected:
int j;
int get_i() const { return i; }
private:
int k;
int get_j() const { return j; }
bool i_be_private() const { return ((i > j) && (k < j)); }
};
Encapsulacion
https://riptutorial.com/es/home 379
class Encapsulator {
int encapsulated;
public:
int get_encapsulated() const { return encapsulated; }
void set_encapsulated(int e) { encapsulated = e; }
void some_func() {
do_something_with(encapsulated);
}
};
Cuando una clase base proporciona un conjunto de funciones sobrecargadas, y una clase
derivada agrega otra sobrecarga al conjunto, esto oculta todas las sobrecargas proporcionadas
por la clase base.
struct HiddenBase {
void f(int) { std::cout << "int" << std::endl; }
void f(bool) { std::cout << "bool" << std::endl; }
void f(std::string) { std::cout << "std::string" << std::endl; }
};
// ...
HiddenBase hb;
HidingDerived hd;
std::string s;
Esto se debe a las reglas de resolución de nombres: durante la búsqueda de nombres, una vez
que se encuentra el nombre correcto, dejamos de buscar, incluso si claramente no hemos
encontrado la versión correcta de la entidad con ese nombre (como en hd.f(s) ); debido a esto, la
https://riptutorial.com/es/home 380
sobrecarga de la función en la clase derivada evita que la búsqueda de nombres descubra las
sobrecargas en la clase base. Para evitar esto, se puede usar una declaración de uso para
"importar" nombres de la clase base a la clase derivada, de modo que estén disponibles durante
la búsqueda de nombres.
// ...
HidingDerived hd;
Si una clase derivada importa nombres con una declaración de uso, pero también declara
funciones con la misma firma que las funciones en la clase base, las funciones de la clase base
serán anuladas u ocultadas silenciosamente.
struct NamesHidden {
virtual void hide_me() {}
virtual void hide_me(float) {}
void hide_me(int) {}
void hide_me(bool) {}
};
También se puede utilizar una declaración de uso para cambiar los modificadores de acceso,
siempre que la entidad importada fuera public o esté protected en la clase base.
struct ProMem {
protected:
void func() {}
};
// ...
ProMem pm;
BecomesPub bp;
https://riptutorial.com/es/home 381
pm.func(); // Error: protected.
bp.func(); // Good.
De manera similar, si queremos llamar explícitamente a una función miembro de una clase
específica en la jerarquía de herencia, podemos calificar el nombre de la función al llamar a la
función, especificando esa clase por nombre.
struct One {
virtual void f() { std::cout << "One." << std::endl; }
};
// ...
Three t;
Las funciones miembro también pueden ser declaradas virtual . En este caso, si se llama a un
puntero o referencia a una instancia, no se accederá directamente a ellos; más bien, buscarán la
función en la tabla de funciones virtuales (una lista de funciones de punteros a miembros para
funciones virtuales, más comúnmente conocida como vtable o vftable ), y la usarán para llamar a
la versión apropiada para la dinámica de la instancia Tipo (real). Si la función se llama
directamente, desde una variable de una clase, no se realiza ninguna búsqueda.
struct Base {
virtual void func() { std::cout << "In Base." << std::endl; }
};
// ...
https://riptutorial.com/es/home 382
Base b;
Derived d;
Tenga en cuenta que si bien pd es Base* , y rd es una Base& , llama func() en cualquiera de las dos
llamadas Derived::func() lugar de Base::func() ; esto se debe a que vtable for Derived actualiza la
entrada Base::func() para que, en cambio, apunte a Derived::func() . A la inversa, observe cómo
pasar una instancia a slicer() siempre da como resultado que se llame a Base::func() , incluso
cuando la instancia pasada es un Derived ; esto se debe a algo conocido como división de datos ,
donde pasar una instancia Derived a un parámetro Base por valor hace que la parte de la instancia
Derived que no es inaccesible a una instancia Base .
Cuando una función de miembro se define como virtual, todas las funciones de miembro de clase
derivadas con la misma firma lo anulan, independientemente de si la función de anulación se
especifica como virtual o no. Sin embargo, esto puede hacer que las clases derivadas sean más
difíciles de analizar para los programadores, ya que no hay ninguna indicación de qué función
(es) es / son virtual .
struct B {
virtual void f() {}
};
struct D : B {
void f() {} // Implicitly virtual, overrides B::f.
// You'd have to check B to know that, though.
};
Sin embargo, tenga en cuenta que una función derivada solo reemplaza una función base si sus
firmas coinciden; incluso si una función derivada se declara explícitamente virtual , en su lugar
creará una nueva función virtual si las firmas no coinciden.
struct BadB {
virtual void f() {}
};
C ++ 11
https://riptutorial.com/es/home 383
A partir de C ++ 11, la intención de anular se puede hacer explícita con la override palabra clave
sensible al contexto. Esto le dice al compilador que el programador espera que anule una función
de clase base, lo que hace que el compilador omita un error si no anula nada.
struct CPP11B {
virtual void f() {}
};
Esto también tiene la ventaja de decirle a los programadores que la función es virtual y también
declarada en al menos una clase base, lo que puede hacer que las clases complejas sean más
fáciles de analizar.
C ++ 11
struct VB {
virtual void f(); // "virtual" goes here.
void g();
};
/* virtual */ void VB::f() {} // Not here.
virtual void VB::g() {} // Error.
Si una clase base sobrecarga una función virtual , solo las sobrecargas que se especifiquen
explícitamente como virtual serán virtuales.
struct BOverload {
virtual void func() {}
void func(int) {}
};
// ...
Const Correccion
https://riptutorial.com/es/home 384
Uno de los usos principales de this calificador de const es la corrección const . Esta es la práctica
de garantizar que solo los accesos que necesitan modificar un objeto puedan modificar el objeto,
y que cualquier función (miembro o no miembro) que no necesite modificar un objeto no tiene
acceso de escritura para eso. objeto (ya sea directa o indirectamente). Esto evita modificaciones
no intencionales, haciendo que el código sea menos propenso a errores. También permite que
cualquier función que no necesite modificar el estado pueda tomar un objeto const o no const , sin
necesidad de reescribir o sobrecargar la función.
const corrección de const , debido a su naturaleza, comienza de abajo hacia arriba: cualquier
función miembro de la clase que no necesite cambiar de estado se declara como const , de modo
que se puede llamar en instancias de const . Esto, a su vez, permite pasar por referencia
parámetros para ser declarado const cuando no necesitan ser modificados, lo que permite que las
funciones que llevan ya sea const o no const objetos sin quejarse, y const -ness puede propagarse
hacia el exterior en este manera. Debido a esto, los captadores suelen estar const , al igual que
cualquier otra función que no necesite modificar el estado lógico.
class ConstIncorrect {
Field fld;
public:
ConstIncorrect(const Field& f) : fld(f) {} // Modifies.
class ConstCorrect {
Field fld;
public:
ConstCorrect(const Field& f) : fld(f) {} // Not const: Modifies.
// ...
https://riptutorial.com/es/home 385
const ConstCorrect but_i_can(make_me_a_field());
// Now, let's read it...
Field f = but_i_can.get_field(); // Good.
but_i_can.do_nothing(); // Good.
Como lo ilustran los comentarios en ConstIncorrect y ConstCorrect , las funciones que califican
para cv también sirven como documentación. Si una clase es const correcta, cualquier función
que no está const puede suponerse con seguridad para cambiar de estado, y cualquier función
que es const puede suponer con seguridad que no cambie de estado.
https://riptutorial.com/es/home 386
Capítulo 61: Futuros y Promesas
Introducción
Las promesas y los futuros se utilizan para transportar un solo objeto de un hilo a otro.
Se puede usar un objeto std::future para recuperar un valor, para probar si un valor está
disponible, o para detener la ejecución hasta que el valor esté disponible.
Examples
std :: futuro y std :: promesa
{
auto promise = std::promise<std::string>();
producer.join();
consumer.join();
}
Este código implementa una versión de std::async , pero se comporta como si async se llamara
siempre con la política de lanzamiento deferred . Esta función tampoco tiene un comportamiento
future especial de async ; El future devuelto puede ser destruido sin adquirir nunca su valor.
template<typename F>
auto async_deferred(F&& func) -> std::future<decltype(func())>
{
using result_type = decltype(func());
std::thread(std::bind([=](std::promise<result_type>& promise)
https://riptutorial.com/es/home 387
{
try
{
promise.set_value(func());
// Note: Will not work with std::promise<void>. Needs some meta-template
programming which is out of scope for this example.
}
catch(...)
{
promise.set_exception(std::current_exception());
}
}, std::move(promise))).detach();
return future;
}
template<typename F>
auto async_deferred(F&& func) -> std::future<decltype(func())>
{
auto task = std::packaged_task<decltype(func())()>(std::forward<F>(func));
auto future = task.get_future();
std::thread(std::move(task)).detach();
return std::move(future);
}
El hilo comienza a ejecutarse inmediatamente. Podemos separarlo o unirlo al final del alcance.
Cuando la llamada de función a std :: thread finaliza, el resultado está listo.
Tenga en cuenta que esto es ligeramente diferente de std::async donde el std::future devuelto
cuando se destruya se bloqueará hasta que el hilo finalice.
Si las restricciones para std :: promise y std :: future no se cumplen, se lanza una excepción de
tipo std :: future_error.
El miembro del código de error en la excepción es de tipo std :: future_errc y los valores son los
siguientes, junto con algunos casos de prueba:
Promesa inactiva
https://riptutorial.com/es/home 388
int test()
{
std::promise<int> pr;
return 0; // returns ok
}
int test()
{
std::promise<int> pr;
auto fut = pr.get_future(); //blocks indefinitely!
return 0;
}
Doble recuperacion:
int test()
{
std::promise<int> pr;
auto fut1 = pr.get_future();
try{
auto fut2 = pr.get_future(); // second attempt to get future
return 0;
}
catch(const std::future_error& e)
{
cout << e.what() << endl; // Error: "The future has already been retrieved
from the promise or packaged_task."
return -1;
}
return fut2.get();
}
int test()
{
std::promise<int> pr;
auto fut = pr.get_future();
try{
std::promise<int> pr2(std::move(pr));
pr2.set_value(10);
pr2.set_value(10); // second attempt to set promise throws exception
}
catch(const std::future_error& e)
{
cout << e.what() << endl; // Error: "The state of the promise has already been
set."
return -1;
}
return fut.get();
}
https://riptutorial.com/es/home 389
En el siguiente ejemplo de clasificación de combinación paralela ingenua, std::async se utiliza
para iniciar múltiples tareas de combinación de combinación paralelas. std::future se usa para
esperar los resultados y sincronizarlos:
#include <iostream>
using namespace std;
while((h<=mid)&&(j<=high))
{
if(num[h]<=num[j])
{
copy[i]=num[h];
h++;
}
else
{
copy[i]=num[j];
j++;
}
i++;
}
if(h>mid)
{
for(k=j;k<=high;k++)
{
copy[i]=num[k];
i++;
}
}
else
{
for(k=h;k<=mid;k++)
{
copy[i]=num[k];
i++;
}
}
for(k=low;k<=high;k++)
swap(num[k],copy[k]);
}
https://riptutorial.com/es/home 390
});
auto future2 = std::async(std::launch::deferred, [&]()
{
merge_sort(mid+1,high,num) ;
});
future1.get();
future2.get();
merge(low,mid,high,num);
}
}
Nota: En el ejemplo std::async se inicia con la directiva std::launch_deferred . Esto es para evitar
que se cree un nuevo hilo en cada llamada. En el caso de nuestro ejemplo, las llamadas a
std::async se hacen fuera de orden, se sincronizan en las llamadas para std::future::get() .
https://riptutorial.com/es/home 391
Capítulo 62: Generación de números
aleatorios
Observaciones
La generación de números aleatorios en C ++ es proporcionada por el encabezado <random> . Este
encabezado define dispositivos aleatorios, generadores pseudoaleatorios y distribuciones.
Examples
Generador de valor aleatorio verdadero
Para generar valores aleatorios verdaderos que se pueden usar para la criptografía
std::random_device se debe usar como generador.
#include <iostream>
#include <random>
int main()
{
std::random_device crypto_random_generator;
std::uniform_int_distribution<int> int_distribution(0,9);
https://riptutorial.com/es/home 392
return 0;
}
#include <iostream>
#include <random>
int main()
{
std::default_random_engine pseudo_random_generator;
std::uniform_int_distribution<int> int_distribution(0, 9);
return 0;
}
Este código crea un generador de números aleatorios y una distribución que genera números
enteros en el rango [0,9] con la misma probabilidad. Luego cuenta cuántas veces se generó cada
resultado.
https://riptutorial.com/es/home 393
El generador de números aleatorios puede (y debe) ser usado para múltiples distribuciones.
#include <iostream>
#include <random>
int main()
{
std::default_random_engine pseudo_random_generator;
std::uniform_int_distribution<int> int_distribution(0, 9);
std::uniform_real_distribution<float> float_distribution(0.0, 1.0);
std::discrete_distribution<int> rigged_dice({1,1,1,1,1,100});
return 0;
}
En este ejemplo, solo se define un generador. Posteriormente se utiliza para generar un valor
aleatorio en tres distribuciones diferentes. La distribución rigged_dice generará un valor entre 0 y
5, pero casi siempre genera un 5 , porque la posibilidad de generar un 5 es 100 / 105 .
https://riptutorial.com/es/home 394
Capítulo 63: Gestión de la memoria
Sintaxis
• : :( opt ) nuevo ( expresión-lista ) ( opt ) new-type-id new-initializer ( opt )
• : :( opt ) nuevo ( expresión-lista ) ( opt ) ( type-id ) new-initializer ( opt )
• : :( opt) borrar fundido expresión
• : :( Opción ) borrar [] expresión-cast
• std :: unique_ptr < type-id > var_name (nuevo type-id ( opt )); // C ++ 11
• std :: shared_ptr < type-id > var_name (nuevo type-id ( opt )); // C ++ 11
• std :: shared_ptr < type-id > var_name = std :: make_shared < type-id > ( opt ); // C ++ 11
• std :: unique_ptr < type-id > var_name = std :: make_unique < type-id > ( opt ); // C ++ 14
Observaciones
Un líder :: obliga a buscar el operador nuevo o de borrado en el ámbito global, anulando
cualquier operador nuevo o de borrado específico de clase sobrecargado.
Los argumentos opcionales que siguen a la new palabra clave se usan generalmente para llamar
ubicación nueva , pero también se pueden usar para pasar información adicional al asignador,
como una etiqueta que solicita que la memoria se asigne de un grupo elegido.
El tipo asignado generalmente se especifica explícitamente, por ejemplo, new Foo , pero también
se puede escribir como auto (desde C ++ 11) o decltype(auto) (desde C ++ 14) para deducirlo del
inicializador.
La inicialización del objeto asignado ocurre de acuerdo con las mismas reglas que la inicialización
de las variables locales. En particular, el objeto se inicializará por defecto si se omite el
inicializador, y cuando se asigna dinámicamente un tipo escalar o una matriz de tipo escalar, no
hay garantía de que la memoria se pondrá a cero.
Un objeto de matriz creado por una nueva expresión se debe destruir utilizando delete[] ,
independientemente de si la nueva expresión se escribió con [] o no. Por ejemplo:
using IA = int[4];
int* pIA = new IA;
delete[] pIA; // right
// delete pIA; // wrong
Examples
Apilar
La pila es una pequeña región de memoria en la que se colocan valores temporales durante la
ejecución. La asignación de datos en la pila es muy rápida en comparación con la asignación de
https://riptutorial.com/es/home 395
almacenamiento dinámico, ya que toda la memoria ya se ha asignado para este propósito.
int main() {
int a = 0; //Stored on the stack
return a;
}
La pila se llama así porque las cadenas de llamadas a funciones tendrán su memoria temporal
"apilada" una encima de la otra, cada una utilizando una pequeña sección separada de la
memoria.
float bar() {
//f will be placed on the stack after anything else
float f = 2;
return f;
}
double foo() {
//d will be placed just after anything within main()
double d = bar();
return d;
}
int main() {
//The stack has no user variables stored in it until foo() is called
return (int)foo();
}
Los datos almacenados en la pila solo son válidos mientras el alcance que asignó la variable aún
esté activo.
int* pA = nullptr;
void foo() {
int b = *pA;
pA = &b;
}
int main() {
int a = 5;
pA = &a;
foo();
//Undefined behavior, the value pointed to by pA is no longer in scope
a = *pA;
}
En C++ el Estándar se refiere a esta área como la Tienda libre, que se considera un término más
preciso.
https://riptutorial.com/es/home 396
Las áreas de memoria asignadas desde Free Store pueden vivir más tiempo que el alcance
original en el que se asignó. Los datos demasiado grandes para ser almacenados en la pila
también pueden asignarse desde Free Store .
La memoria sin formato se puede asignar y desasignar mediante las palabras clave new y delete .
delete foo; // Deletes the memory for the float at pF, invalidating the pointer
foo = nullptr; // Setting the pointer to nullptr after delete is often considered good
practice
También es posible asignar matrices de tamaño fijo con nuevas y eliminar , con una sintaxis
ligeramente diferente. La asignación de arrays no es compatible con la asignación sin arrays, y
mezclar los dos llevará a la corrupción del montón. La asignación de una matriz también asigna
memoria para rastrear el tamaño de la matriz para su posterior eliminación de una manera
definida por la implementación.
Al usar new y delete en lugar de malloc y free , el constructor y el destructor se ejecutarán (similar
a los objetos basados en la pila). Esta es la razón por la cual nuevo y eliminar son preferidos a
malloc y gratis .
struct ComplexType {
int a = 0;
C ++ 11
C ++ 14
Colocación nueva
https://riptutorial.com/es/home 397
Hay situaciones en las que no queremos confiar en Free Store para asignar memoria y queremos
usar asignaciones de memoria personalizadas utilizando new .
Para estas situaciones, podemos usar Placement New , donde podemos decirle al operador 'nuevo'
que asigne memoria desde una ubicación de memoria asignada
Por ejemplo
int a4byteInteger;
En este ejemplo, la memoria apuntada por a4byteChar es de 4 bytes asignados a 'pila' a través de
la variable entera a4byteInteger .
El mismo comportamiento se puede lograr para la memoria asignada dinámica también. Por
ejemplo
struct ComplexType {
int a;
ComplexType() : a(0) {}
~ComplexType() {}
};
int main() {
char* dynArray = new char[256];
new((void*)localArray) ComplexType();
https://riptutorial.com/es/home 398
//Only need to call the destructor for stack memory
reinterpret_cast<ComplexType*>(localArray)->~ComplexType();
return 0;
}
https://riptutorial.com/es/home 399
Capítulo 64: Herramientas y Técnicas de
Depuración y Prevención de Depuración de C
++
Introducción
Mucho tiempo de los desarrolladores de C ++ se dedica a la depuración. El objetivo de este tema
es ayudar con esta tarea y proporcionar inspiración para las técnicas. No espere una lista extensa
de problemas y soluciones corregidos por las herramientas o un manual sobre las herramientas
mencionadas.
Observaciones
Este tema aún no está completo, los ejemplos sobre las siguientes técnicas / herramientas serían
útiles:
Examples
Mi programa de C ++ termina con segfault - valgrind
#include <iostream>
void fail() {
int *p1;
int *p2(NULL);
int *p3 = p1;
if (p3) {
std::cout << *p3 << std::endl;
}
}
int main() {
fail();
}
https://riptutorial.com/es/home 400
Correr:
$ ./main
Segmentation fault (core dumped)
$
$ valgrind ./main
==8515== Memcheck, a memory error detector
==8515== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==8515== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==8515== Command: ./main
==8515==
==8515== Conditional jump or move depends on uninitialised value(s)
==8515== at 0x400813: fail() (main.cpp:7)
==8515== by 0x40083F: main (main.cpp:13)
==8515==
==8515== Invalid read of size 4
==8515== at 0x400819: fail() (main.cpp:8)
==8515== by 0x40083F: main (main.cpp:13)
==8515== Address 0x0 is not stack'd, malloc'd or (recently) free'd
==8515==
==8515==
==8515== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==8515== Access not within mapped region at address 0x0
==8515== at 0x400819: fail() (main.cpp:8)
==8515== by 0x40083F: main (main.cpp:13)
==8515== If you believe this happened as a result of a stack
==8515== overflow in your program's main thread (unlikely but
==8515== possible), you can try to increase the size of the
==8515== main thread stack using the --main-stacksize= flag.
==8515== The main thread stack size used in this run was 8388608.
==8515==
==8515== HEAP SUMMARY:
==8515== in use at exit: 72,704 bytes in 1 blocks
==8515== total heap usage: 1 allocs, 0 frees, 72,704 bytes allocated
==8515==
==8515== LEAK SUMMARY:
==8515== definitely lost: 0 bytes in 0 blocks
==8515== indirectly lost: 0 bytes in 0 blocks
==8515== possibly lost: 0 bytes in 0 blocks
==8515== still reachable: 72,704 bytes in 1 blocks
==8515== suppressed: 0 bytes in 0 blocks
==8515== Rerun with --leak-check=full to see details of leaked memory
==8515==
==8515== For counts of detected and suppressed errors, rerun with: -v
==8515== Use --track-origins=yes to see where uninitialised values come from
==8515== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
$
https://riptutorial.com/es/home 401
La primera línea nos dice que segfault es causado por la lectura de 4 bytes. Las líneas segunda y
tercera son de pila de llamadas. Significa que la lectura no válida se realiza en la función fail() ,
línea 8 de main.cpp, a la que llama main, línea 13 de main.cpp.
Pero primero revisamos el puntero, entonces, ¿qué pasa? Veamos el otro bloque:
if (p3) {
Lo que nos señala la línea donde comprobamos p3 en lugar de p2. Pero, ¿cómo es posible que
p3 no esté inicializado? Lo inicializamos por:
Lo que nos dice que el valor no inicializado que usamos en la línea 7 se creó en la línea 3:
int *p1;
https://riptutorial.com/es/home 402
#include <iostream>
void fail() {
int *p1;
int *p2(NULL);
int *p3 = p1;
if (p3) {
std::cout << *p2 << std::endl;
}
}
int main() {
fail();
}
gdb ./main
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/opencog/code-snippets/stackoverflow/a.out
Vemos que la falla de segmentación está ocurriendo en la línea 11. Por lo tanto, la única variable
que se usa en esta línea es el puntero p2. Permite examinar su contenido escribiendo print.
(gdb) print p2
$1 = (int *) 0x0
Ahora vemos que p2 se inicializó a 0x0, lo que significa NULL. En esta línea, sabemos que
estamos intentando eliminar la referencia de un puntero NULO. Así que vamos y lo arreglamos.
Código limpio
La depuración comienza con la comprensión del código que está intentando depurar.
Código incorrecto:
int main() {
int value;
std::vector<int> vectorToSort;
https://riptutorial.com/es/home 403
vectorToSort.push_back(42); vectorToSort.push_back(13);
for (int i = 52; i; i = i - 1)
{
vectorToSort.push_back(i *2);
}
/// Optimized for sorting small vectors
if (vectorToSort.size() == 1);
else
{
if (vectorToSort.size() <= 2)
std::sort(vectorToSort.begin(), std::end(vectorToSort));
}
for (value : vectorToSort) std::cout << value << ' ';
return 0; }
Mejor código:
std::vector<int> createSemiRandomData() {
std::vector<int> data;
data.push_back(42);
data.push_back(13);
for (int i = 52; i; --i)
vectorToSort.push_back(i *2);
return data;
}
std::sort(vectorToSort.begin(), vectorToSort.end());
}
int main() {
auto vectorToSort = createSemiRandomData();
sortVector(std::ref(vectorToSort));
printVector(vectorToSort);
return 0;
}
Mirando el código anterior, uno puede identificar un par de mejoras para mejorar la legibilidad y la
depuración:
https://riptutorial.com/es/home 404
El uso de funciones separadas le permite saltear algunas funciones en el depurador si no está
interesado en los detalles. En este caso específico, es posible que no esté interesado en la
creación o impresión de los datos y solo desee ingresar en la clasificación.
Otra ventaja es que necesita leer menos código (y memorizarlo) mientras recorre el código. Ahora
solo necesita leer 3 líneas de código en main() para poder entenderlo, en lugar de toda la función.
La tercera ventaja es que simplemente tiene menos código que ver, lo que ayuda a un ojo
entrenado a detectar este error en segundos.
Como el código de formato es una tarea que requiere mucho tiempo, se recomienda usar una
herramienta dedicada para esto. La mayoría de los IDE tienen al menos algún tipo de soporte
para esto y pueden hacer un formateo más consistente que los humanos.
Es posible que tenga en cuenta que el estilo no se limita a espacios y líneas nuevas, ya que ya no
mezclamos el estilo libre y las funciones miembro para comenzar / finalizar el contenedor. (
v.begin() vs std::end(v) ).
Conclusión
Tener un código limpio lo ayudará a comprender el código y reducirá el tiempo que necesita para
depurarlo. En el segundo ejemplo, un revisor de código podría incluso detectar el error a primera
vista, mientras que el error podría estar oculto en los detalles del primero. (PS: El error está en la
comparación con 2 )
Análisis estático
El análisis estático es la técnica mediante la cual el código comprueba los patrones vinculados a
errores conocidos. Sin embargo, el uso de esta técnica requiere menos tiempo que la revisión de
https://riptutorial.com/es/home 405
un código, sin embargo, sus verificaciones solo se limitan a las programadas en la herramienta.
Las comprobaciones pueden incluir el punto y coma incorrecto detrás de la sentencia if (var); (
if (var); ) hasta algoritmos de gráficos avanzados que determinan si una variable no está
inicializada.
Si habilita estas opciones, notará que cada compilador encontrará errores que otros no, y que
obtendrá errores en técnicas que podrían ser válidas en un contexto específico. while
(staticAtomicBool); podría ser aceptable incluso si while (localBool); no es
Así que, a diferencia de la revisión de código, estás luchando contra una herramienta que
entiende tu código, te dice muchos errores útiles y, a veces, no está de acuerdo contigo. En este
último caso, es posible que deba suprimir la advertencia localmente.
Como las opciones anteriores habilitan todas las advertencias, pueden habilitar las advertencias
que no desea. (¿Por qué debería ser compatible su código con C ++ 98?) Si es así, simplemente
puede desactivar esa advertencia específica:
Cuando las advertencias del compilador le ayudan durante el desarrollo, ralentizan bastante la
compilación. Es por eso que es posible que no siempre desee habilitarlos de forma
predeterminada. O los ejecuta de forma predeterminada o habilita cierta integración continua con
los cheques más caros (o todos ellos).
Herramientas externas
Si decide tener alguna integración continua, el uso de otras herramientas no es tan difícil. Una
herramienta como clang-tidy tiene una lista de comprobaciones que cubre una amplia gama de
problemas, algunos ejemplos:
• Errores reales
○Prevención de rebanar
○Afirma con efectos secundarios.
• Controles de legibilidad
○Sangría engañosa
○Comprobar el nombre del identificador
• Controles de modernización
https://riptutorial.com/es/home 406
○ Utilice make_unique ()
○ Utilizar nullptr
• Verificaciones de rendimiento
○ Encuentra copias innecesarias
○ Encuentra ineficientes llamadas de algoritmo
Es posible que la lista no sea tan grande, ya que Clang ya tiene muchas advertencias de
compilación, sin embargo, te acercará un paso más a una base de código de alta calidad.
Otras herramientas
Existen otras herramientas con fines similares, como:
Conclusión
Existen muchas herramientas de análisis estático para C ++, ambas integradas en el compilador
como herramientas externas. Probarlos no toma mucho tiempo para configuraciones fáciles y
encontrarán errores que podría pasar por alto en la revisión del código.
Las corrupciones de la pila son errores molestos a la vista. Como la pila está dañada, el
depurador a menudo no puede darle un buen seguimiento de la pila de dónde se encuentra y
cómo llegó allí.
Aquí es donde entra en juego la pila segura. En lugar de usar una sola pila para tus hilos, usará
dos: una pila segura y una pila peligrosa. La pila segura funciona exactamente igual que antes,
excepto que algunas partes se mueven a la pila peligrosa.
Como resultado, cualquier operación que realice con esos punteros, cualquier modificación que
realice en la memoria (basada en esos punteros / referencias) solo puede afectar a la memoria en
la segunda pila. Como uno nunca recibe un puntero que está cerca de la pila segura, la pila no
puede corromper la pila y el depurador aún puede leer todas las funciones de la pila para dar un
buen seguimiento.
https://riptutorial.com/es/home 407
La pila segura no se inventó para darle una mejor experiencia de depuración, sin embargo, es un
efecto secundario agradable para los insectos desagradables. Su propósito original es como parte
del Proyecto de integridad de puntero de código (CPI) , en el que intentan evitar la anulación de
las direcciones de retorno para evitar la inyección de código. En otras palabras, intentan evitar la
ejecución de un código de hackers.
Por este motivo, la función se ha activado en cromo y se ha informado que tiene una sobrecarga
de CPU <1%.
¿Cómo habilitarlo?
En este momento, la opción solo está disponible en el compilador -fsanitize=safe-stack , donde
se puede pasar -fsanitize=safe-stack al compilador. Se hizo una propuesta para implementar la
misma característica en GCC.
Conclusión
La corrupción de la pila puede ser más fácil de depurar cuando la pila segura está habilitada.
Debido a una baja sobrecarga de rendimiento, incluso puede activarse de forma predeterminada
en la configuración de su compilación.
https://riptutorial.com/es/home 408
Capítulo 65: Idioma Pimpl
Observaciones
El idioma pimpl (p ointer a impl ementation, a veces se hace referencia a la técnica puntero o
gato de Cheshire como opaco), reduce los tiempos de compilación de una clase moviendo todos
sus miembros de datos privados en una estructura definida en el archivo .cpp.
La clase posee un puntero a la implementación. De esta manera, se puede reenviar, para que el
archivo de encabezado no necesite #include clases que se usan en las variables de miembros
privados.
Cuando se utiliza el lenguaje pimpl, cambiar un miembro de datos privados no requiere volver a
compilar las clases que dependen de él.
Examples
Lenguaje básico de Pimpl
C ++ 11
En el archivo de cabecera:
// widget.h
class Widget
{
public:
Widget();
~Widget();
void DoSomething();
private:
// the pImpl idiom is named after the typical variable name used
// ie, pImpl:
struct Impl; // forward declaration
std::experimental::propagate_const<std::unique_ptr< Impl >> pImpl; // ptr to actual
implementation
};
En el archivo de implementación:
// widget.cpp
#include "widget.h"
#include "reallycomplextype.h" // no need to include this header inside widget.h
https://riptutorial.com/es/home 409
struct Widget::Impl
{
// the attributes needed from Widget go here
ReallyComplexType rct;
};
Widget::Widget() :
pImpl(std::make_unique<Impl>())
{}
Widget::~Widget() = default;
void Widget::DoSomething()
{
// do the stuff here with pImpl
}
La pImpl contiene el estado del Widget (o parte / la mayor parte de él). En lugar de que la
descripción del estado del Widget se exponga en el archivo de encabezado, solo se puede
exponer dentro de la implementación.
Peligro: unique_ptr cuenta que para que esto funcione con unique_ptr , ~Widget() debe
implementarse en un punto en un archivo donde el Impl sea completamente visible. Puede
=default allí, pero si =default donde Impl no está definido, el programa puede fácilmente tener un
formato incorrecto, no se requiere diagnóstico.
https://riptutorial.com/es/home 410
Capítulo 66: Implementación de patrones de
diseño en C ++
Introducción
En esta página, puede encontrar ejemplos de cómo se implementan los patrones de diseño en C
++. Para obtener detalles sobre estos patrones, puede consultar la documentación de patrones de
diseño .
Observaciones
Un patrón de diseño es una solución general reutilizable para un problema que ocurre
comúnmente dentro de un contexto dado en el diseño de software.
Examples
Patrón observador
La intención de Observer Pattern es definir una dependencia de uno a muchos entre objetos para
que cuando un objeto cambie de estado, todos sus dependientes sean notificados y actualizados
automáticamente.
El sujeto y los observadores definen la relación uno a muchos. Los observadores dependen del
tema, de modo que cuando el estado del sujeto cambia, los observadores reciben una
notificación. Dependiendo de la notificación, los observadores también pueden actualizarse con
nuevos valores.
#include <iostream>
#include <vector>
class Subject;
class Observer
{
public:
virtual ~Observer() = default;
virtual void Update(Subject&) = 0;
};
class Subject
{
public:
virtual ~Subject() = default;
void Attach(Observer& o) { observers.push_back(&o); }
void Detach(Observer& o)
{
https://riptutorial.com/es/home 411
observers.erase(std::remove(observers.begin(), observers.end(), &o));
}
void Notify()
{
for (auto* o : observers) {
o->Update(*this);
}
}
private:
std::vector<Observer*> observers;
};
Notify();
}
private:
int hour;
int minute;
int second;
};
void Draw()
{
int hour = subject.GetHour();
int minute = subject.GetMinute();
int second = subject.GetSecond();
private:
ClockTimer& subject;
};
https://riptutorial.com/es/home 412
class AnalogClock: public Observer
{
public:
explicit AnalogClock(ClockTimer& s) : subject(s) { subject.Attach(*this); }
~AnalogClock() { subject.Detach(*this); }
void Update(Subject& theChangedSubject) override
{
if (&theChangedSubject == &subject) {
Draw();
}
}
void Draw()
{
int hour = subject.GetHour();
int minute = subject.GetMinute();
int second = subject.GetSecond();
int main()
{
ClockTimer timer;
DigitalClock digitalClock(timer);
AnalogClock analogClock(timer);
Salida:
1. Los objetos (objeto DigitalClock o AnalogClock ) utilizan las interfaces de Asunto ( Attach() o
Detach() ) para suscribirse (registrarse) como observadores o anular la suscripción (eliminar)
de ser observadores ( subject.Attach(*this); subject.Detach(*this);
3. Todos los observadores necesitan implementar la interfaz de observador. Esta interfaz solo
tiene un método, Update() , que se llama cuando cambia el estado del sujeto ( Update(Subject
&) )
https://riptutorial.com/es/home 413
Subject::Attach (Observer&) , void Subject::Detach(Observer&) y void Subject::Notify() .
5. El objeto concreto también puede tener métodos para establecer y obtener su estado.
6. Los observadores concretos pueden ser de cualquier clase que implemente la interfaz
Observer. Cada observador se suscribe (registra) con un sujeto concreto para recibir la
actualización ( subject.Attach(*this); ).
7. Los dos objetos de Observer Pattern están ligeramente acoplados , pueden interactuar
pero con poco conocimiento el uno del otro.
Variación:
Señal y ranuras
Signals and Slots es una construcción de lenguaje introducida en Qt, que facilita la
implementación del patrón Observer y evita el código repetitivo. El concepto es que los controles
(también conocidos como widgets) pueden enviar señales que contienen información de eventos
que pueden ser recibidos por otros controles utilizando funciones especiales conocidas como
ranuras. La ranura en Qt debe ser un miembro de la clase declarado como tal. El sistema de
señal / ranura encaja bien con la forma en que se diseñan las interfaces gráficas de usuario. De
manera similar, el sistema de señal / ranura se puede usar para notificaciones de eventos de E /
S asíncronas (incluidos sockets, tuberías, dispositivos serie, etc.) o para asociar eventos de
tiempo de espera con instancias de objetos, métodos o funciones apropiadas. No es necesario
escribir ningún código de registro / cancelación de registro / invocación, porque el Meta Object
Compiler (MOC) de Qt genera automáticamente la infraestructura necesaria.
El lenguaje C # también admite una construcción similar, aunque con una terminología y una
sintaxis diferentes: los eventos desempeñan el papel de las señales y los delegados son los
espacios. Además, un delegado puede ser una variable local, como un puntero a una función,
mientras que una ranura en Qt debe ser un miembro de la clase declarado como tal.
Patrón de adaptador
Convertir la interfaz de una clase en otra interfaz que los clientes esperan. El adaptador (o
Wrapper) permite que las clases trabajen juntas y que de otra manera no podrían debido a
interfaces incompatibles. La motivación del patrón de adaptador es que podemos reutilizar el
software existente si podemos modificar la interfaz.
4. En STL, pila adaptada del vector: cuando la pila ejecuta push (), el vector subyacente hace
vector :: push_back ().
Ejemplo:
https://riptutorial.com/es/home 414
#include <iostream>
// Adapter wrapper
class RectangleAdapter: public Rectangle, private LegacyRectangle
{
public:
RectangleAdapter(int x, int y, int w, int h):
LegacyRectangle(x, y, x + w, y + h) {
std::cout << "RectangleAdapter(x,y,x+w,x+h)\n";
}
void draw() {
std::cout << "RectangleAdapter: draw().\n";
oldDraw();
}
};
int main()
{
int x = 20, y = 50, w = 300, h = 200;
Rectangle *r = new RectangleAdapter(x,y,w,h);
r->draw();
}
//Output:
//LegacyRectangle(x1,y1,x2,y2)
//RectangleAdapter(x,y,x+w,x+h)
https://riptutorial.com/es/home 415
2. El objetivo es la clase Rectangle . Esto es en lo que el cliente invoca el método.
5. La clase LegacyRectangle no tiene los mismos métodos ( draw() ) que Rectangle , pero el
Adapter(RectangleAdapter) puede tomar las llamadas al método Rectangle y girar e invocar el
método en el LegacyRectangle , oldDraw() .
void draw() {
std::cout << "RectangleAdapter: draw().\n";
oldDraw();
}
};
El patrón de diseño del adaptador traduce la interfaz para una clase en una interfaz compatible
pero diferente. Por lo tanto, esto es similar al patrón de proxy en que es un contenedor de un solo
componente. Pero la interfaz para la clase de adaptador y la clase original pueden ser diferentes.
Como hemos visto en el ejemplo anterior, este patrón de adaptador es útil para exponer una
interfaz diferente para una API existente para permitir que funcione con otro código. Además, al
usar un patrón de adaptador, podemos tomar interfaces heterogéneas y transformarlas para
proporcionar una API consistente.
El patrón de Bridge tiene una estructura similar a un adaptador de objetos, pero Bridge tiene una
intención diferente: está destinado a separar una interfaz de su implementación para que puedan
variarse de forma fácil e independiente. Un adaptador está destinado a cambiar la interfaz de
un objeto existente .
Patrón de fábrica
El patrón de fábrica desacopla la creación de objetos y permite la creación por nombre utilizando
una interfaz común:
https://riptutorial.com/es/home 416
class Animal{
public:
virtual std::shared_ptr<Animal> clone() const = 0;
virtual std::string getname() const = 0;
};
class AnimalFactory{
public:
static std::shared_ptr<Animal> getAnimal( const std::string& name )
{
if ( name == "bear" )
return std::make_shared<Bear>();
if ( name == "cat" )
return std::shared_ptr<Cat>();
return nullptr;
}
};
El patrón del generador desacopla la creación del objeto del propio objeto. La idea principal detrás
es que un objeto no tiene que ser responsable de su propia creación . El ensamblaje correcto
y válido de un objeto complejo puede ser una tarea complicada en sí misma, por lo que esta tarea
puede delegarse a otra clase.
Inspirado por el Email Builder en C # , he decidido hacer una versión de C ++ aquí. Un objeto de
correo electrónico no es necesariamente un objeto muy complejo , pero puede demostrar el
patrón.
https://riptutorial.com/es/home 417
#include <iostream>
#include <sstream>
#include <string>
class Email
{
public:
friend class EmailBuilder; // the builder can access Email's privates
private:
Email() = default; // restrict construction to builder
string m_from;
string m_to;
string m_subject;
string m_body;
};
class EmailBuilder
{
public:
EmailBuilder& from(const string &from) {
m_email.m_from = from;
return *this;
}
operator Email&&() {
return std::move(m_email); // notice the move
}
https://riptutorial.com/es/home 418
private:
Email m_email;
};
EmailBuilder Email::make()
{
return EmailBuilder();
}
// Bonus example!
std::ostream& operator <<(std::ostream& stream, const Email& email)
{
stream << email.to_string();
return stream;
}
int main()
{
Email mail = Email::make().from("[email protected]")
.to("[email protected]")
.subject("C++ builders")
.body("I like this API, don't you?");
Para versiones anteriores de C ++, uno puede simplemente ignorar la operación std::move y
eliminar el && del operador de conversión (aunque esto creará una copia temporal).
El constructor finaliza su trabajo cuando libera el correo electrónico creado por el operator
Email&&() . En este ejemplo, el constructor es un objeto temporal y devuelve el correo electrónico
antes de ser destruido. También puede usar una operación explícita como Email
EmailBuilder::build() {...} lugar del operador de conversión.
int main()
https://riptutorial.com/es/home 419
{
EmailBuilder builder;
add_addresses(builder);
compose_mail(builder);
https://riptutorial.com/es/home 420
Capítulo 67: Incompatibilidades C
Introducción
Esto describe qué código C se romperá en un compilador de C ++.
Examples
Palabras clave reservadas
El primer ejemplo son palabras clave que tienen un propósito especial en C ++: lo siguiente es
legal en C, pero no en C ++.
int class = 5
En C, los punteros se pueden convertir en un void* , que necesita una conversión explícita en C
++. Lo siguiente es ilegal en C ++, pero legal en C:
void* ptr;
int* intptr = ptr;
Agregar un reparto explícito hace que esto funcione, pero puede causar problemas adicionales.
goto o cambiar
En C ++, no puede omitir las inicializaciones con goto o switch . Lo siguiente es válido en C, pero
no en C ++:
goto foo;
int skipped = 1;
foo;
https://riptutorial.com/es/home 421
Capítulo 68: Inferencia de tipos
Introducción
Este tema trata sobre la inferencia de tipos que implica el tipo auto palabra clave que está
disponible en C ++ 11.
Observaciones
Por lo general, es mejor declarar const , & y constexpr siempre que use auto si alguna vez se
requiere para prevenir comportamientos no deseados como copiar o mutaciones. Esos consejos
adicionales aseguran que el compilador no genere ninguna otra forma de inferencia. Tampoco es
aconsejable el uso excesivo auto y debe utilizarse solo cuando la declaración real es muy larga,
especialmente con las plantillas STL.
Examples
Tipo de datos: Auto
Este ejemplo muestra las inferencias de tipo básico que puede realizar el compilador.
auto a = 1; // a = int
auto b = 2u; // b = unsigned int
auto c = &a; // c = int*
const auto d = c; // d = const int*
const auto& e = b; // e = const unsigned int&
Sin embargo, la palabra clave auto no siempre realiza la inferencia de tipo esperada sin
sugerencias adicionales para & o const o constexpr
// y = unsigned int,
// note that y does not infer as const unsigned int&
// The compiler would have generated a copy instead of a reference value to e or b
auto y = e;
Lambda auto
La palabra clave auto del tipo de datos es una forma conveniente para que los programadores
declaren funciones lambda. Ayuda al acortar la cantidad de texto que los programadores
necesitan escribir para declarar un puntero a función.
https://riptutorial.com/es/home 422
// Do this is of type (int)(*DoThis)(int, int)
// else we would have to write this long
int(*pDoThis)(int, int)= [](int a, int b) { return a + b; };
Bucles y auto
Este ejemplo muestra cómo se puede usar auto para acortar la declaración de tipos para los
bucles
https://riptutorial.com/es/home 423
Capítulo 69: Internacionalización en C ++
Observaciones
El lenguaje C ++ no dicta ningún conjunto de caracteres, algunos compiladores pueden admitir el
uso de UTF-8, o incluso de UTF-16. Sin embargo, no hay certeza de que se proporcione algo más
que simples caracteres ANSI / ASCII.
Por lo tanto, toda la compatibilidad con idiomas internacionales está definida por la
implementación, y depende de qué plataforma, sistema operativo y compilador esté utilizando.
Varias bibliotecas de terceros (como la Biblioteca del Comité Internacional de Unicode) que
pueden utilizarse para ampliar el soporte internacional de la plataforma.
Examples
Entendiendo las características de la cadena C ++
#include <iostream>
#include <string>
int main()
{
const char * C_String = "This is a line of text w";
const char * C_Problem_String = "This is a line of text ኚ";
std::string Std_String("This is a second line of text w");
std::string Std_Problem_String("This is a second line of ϵx ኚ");
Según la plataforma (Windows, OSX, etc.) y el compilador (GCC, MSVC, etc.), este programa
puede no compilar, mostrar diferentes valores o mostrar los mismos valores .
Longitud de la cuerda: 31
Longitud de la cuerda: 31
Longitud CString: 24
Longitud CString: 24
Esto muestra que bajo MSVC cada uno de los caracteres extendidos utilizados se considera un
"carácter" único, y esta plataforma es totalmente compatible con los idiomas internacionalizados.
Sin embargo, debe tenerse en cuenta que este comportamiento es inusual, estos caracteres
https://riptutorial.com/es/home 424
internacionales se almacenan internamente como Unicode y, por lo tanto, tienen varios bytes de
longitud. Esto puede causar errores inesperados.
Longitud de la cuerda: 31
Longitud de la cuerda: 36
Longitud CString: 24
Longitud CString: 26
Este ejemplo demuestra que si bien el compilador GCC utilizado en esta plataforma (Linux)
admite estos caracteres extendidos, también usa ( correctamente) varios bytes para almacenar un
carácter individual.
En este caso, es posible el uso de caracteres Unicode, pero el programador debe tener mucho
cuidado al recordar que la longitud de una "cadena" en este escenario es la longitud en bytes ,
no la longitud en caracteres legibles .
Estas diferencias se deben a la forma en que se manejan los idiomas internacionales por
plataforma y, lo que es más importante, que las cadenas C y C ++ utilizadas en este ejemplo
pueden considerarse una matriz de bytes , por lo que (para este uso) el lenguaje C ++
considera Un carácter (char) para ser un solo byte .
https://riptutorial.com/es/home 425
Capítulo 70: Iteración
Examples
descanso
continuar
int sum = 0;
for (int i = 0; i < N; i++) {
int x;
std::cin >> x;
if (x < 0) continue;
sum += x;
// equivalent to: if (x >= 0) sum += x;
}
hacer
para
// print 10 asterisks
for (int i = 0; i < 10; i++) {
https://riptutorial.com/es/home 426
putchar('*');
}
mientras
int i = 0;
// print 10 asterisks
while (i < 10) {
putchar('*');
i++;
}
https://riptutorial.com/es/home 427
Capítulo 71: Iteradores
Examples
Iteradores C (Punteros)
#ifdef BEFORE_CPP11
// You can use `sizeof` to determine how many elements are in an array.
const int* first = array;
const int* afterLast = first + sizeof(array) / sizeof(array[0]);
// Then you can iterate over the array by incrementing a pointer until
// it reaches past the end of our array.
for (const int* i = first; i < afterLast; ++i) {
std::cout << *i << std::endl;
}
#else
// With C++11, you can let the STL compute the start and end iterators:
for (auto i = std::begin(array); i != std::end(array); ++i) {
std::cout << *i << std::endl;
}
#endif
Este código emitiría los números del 1 al 5, uno en cada línea de la siguiente manera:
1
2
3
4
5
Rompiendolo
Esta línea crea una nueva matriz de enteros con 5 valores. Las matrices C son solo punteros a la
memoria donde cada valor se almacena junto en un bloque contiguo.
Estas líneas crean dos punteros. El primer puntero recibe el valor del puntero de matriz, que es la
dirección del primer elemento de la matriz. El operador sizeof cuando se utiliza en una matriz C
https://riptutorial.com/es/home 428
devuelve el tamaño de la matriz en bytes. Dividido por el tamaño de un elemento, se obtiene el
número de elementos en la matriz. Podemos usar esto para encontrar la dirección del bloque
después de la matriz.
Aquí creamos un puntero que usaremos como iterador. Se inicia con la dirección del primer
elemento que queremos para repetir, y vamos a seguir para recorrer todo el tiempo que i es
menor que afterLast , lo que significa el tiempo que i está apuntando a una dirección dentro de
array .
Por último, dentro del bucle podemos acceder al valor al que apunta nuestro iterador i mediante
la anulación de la referencia. Aquí el operador de desreferencia * devuelve el valor en la dirección
en i .
Visión general
A B C
+---+---+---+---+
| A | B | C | |
+---+---+---+---+
Los elementos son cosas dentro de una secuencia. Las posiciones son lugares donde pueden
suceder operaciones significativas a la secuencia. Por ejemplo, uno se inserta en una posición,
antes o después del elemento A, no en un elemento. Incluso la eliminación de un elemento (
erase(A) ) se realiza primero encontrando su posición y luego eliminándola.
https://riptutorial.com/es/home 429
Uno puede pensar en un iterador como una desreferencia al valor al que se refiere en la
secuencia. Esto es especialmente útil para comprender por qué nunca debe desreferenciar el
iterador end() en una secuencia:
+---+---+---+---+
| A | B | C | |
+---+---+---+---+
↑ ↑
| +-- An iterator here has no value. Do not dereference it!
+-------------- An iterator here dereferences to the value A.
+---+---+---+---+
| A | B | C | |
+---+---+---+---+
↑ ↑
| |
+- first +- last
También es posible obtener un iterador para cualquier secuencia , porque incluso una secuencia
vacía contiene al menos una posición:
+---+
| |
+---+
En una secuencia vacía, begin() y end() estarán en la misma posición, y ninguna de ellas puede
ser referenciada:
+---+
| |
+---+
↑
|
+- empty_sequence.begin()
|
+- empty_sequence.end()
La visualización alternativa de los iteradores es que marcan las posiciones entre los elementos:
+---+---+---+
| A | B | C |
+---+---+---+
↑ ^ ^ ↑
| |
+- first +- last
https://riptutorial.com/es/home 430
después del iterador. Algunas situaciones donde esta vista es particularmente útil son:
Iteradores inválidos
Un iterador se invalida si (por ejemplo, en el curso de una operación) su posición ya no forma
parte de una secuencia. No se puede anular la referencia a un iterador invalidado hasta que se
haya reasignado a una posición válida. Por ejemplo:
std::vector<int>::iterator first;
{
std::vector<int> foo;
first = foo.begin(); // first is now valid
} // foo falls out of scope and is destroyed
// At this point first is now invalid
Tenga en cuenta que el segundo argumento de std :: distance debe ser accesible desde el
primero (o, en otras palabras, first debe ser menor o igual que el second ).
Aunque puede realizar operadores aritméticos con iteradores, no todas las operaciones están
definidas para todos los tipos de iteradores. a = b + 3; funcionaría para los iteradores de acceso
https://riptutorial.com/es/home 431
aleatorio, pero no funcionaría para los iteradores delanteros o bidireccionales, que aún pueden
avanzar en 3 posiciones con algo como b = a; ++b; ++b; ++b; . Por lo tanto, se recomienda utilizar
funciones especiales en caso de que no esté seguro de qué tipo de iterador (por ejemplo, en una
función de plantilla que acepta iterador).
Conceptos de iterador
El estándar de C ++ describe varios conceptos diferentes de iteradores. Estos se agrupan de
acuerdo a cómo se comportan en las secuencias a las que se refieren. Si conoce el concepto que
modela un iterador (se comporta como), puede estar seguro del comportamiento de ese iterador
independientemente de la secuencia a la que pertenezca . A menudo se describen en orden de la
más a la menos restrictiva (porque el siguiente concepto de iterador es un paso mejor que su
predecesor):
• Iteradores de entrada: pueden ser referenciados solo una vez por posición. Solo puede
avanzar, y solo una posición a la vez.
• Iteradores de avance: un iterador de entrada que puede ser referenciado en cualquier
cantidad de veces.
• Iteradores bidireccionales: un iterador hacia adelante que también puede avanzar hacia
atrás una posición a la vez.
• Iteradores de acceso aleatorio: un iterador bidireccional que puede avanzar o retroceder
cualquier número de posiciones a la vez.
• Iteradores contiguos (desde C ++ 17): un iterador de acceso aleatorio que garantiza que los
datos subyacentes son contiguos en la memoria.
Los algoritmos pueden variar según el concepto modelado por los iteradores que se les dan. Por
ejemplo, aunque random_shuffle se puede implementar para los iteradores hacia adelante, se
podría proporcionar una variante más eficiente que requiera iteradores de acceso aleatorio.
template<class Iter>
Iter find(Iter first, Iter last, typename std::iterator_traits<Iter>::value_type val) {
while (first != last) {
if (*first == val)
return first;
++first;
}
return last;
}
https://riptutorial.com/es/home 432
template<class BidirIt>
void test(BidirIt a, std::bidirectional_iterator_tag) {
std::cout << "Bidirectional iterator is used" << std::endl;
}
template<class ForwIt>
void test(ForwIt a, std::forward_iterator_tag) {
std::cout << "Forward iterator is used" << std::endl;
}
template<class Iter>
void test(Iter a) {
test(a, typename std::iterator_traits<Iter>::iterator_category());
}
Las categorías de iteradores son básicamente conceptos de iteradores, excepto que los
iteradores contiguos no tienen su propia etiqueta, ya que se encontró que rompe el código.
Iteradores inversos
Si queremos iterar hacia atrás a través de una lista o vector, podemos usar un reverse_iterator .
Un iterador inverso se realiza a partir de un iterador de acceso aleatorio o bidireccional que
mantiene como miembro al que se puede acceder a través de base() .
Para iterar hacia atrás, utilice rbegin() y rend() como los iteradores para el final de la colección y
el inicio de la colección, respectivamente.
std::vector<int>::reverse_iterator r = v.rbegin();
std::vector<int>::iterator i = r.base();
assert(&*r == &*(i-1)); // always true if r, (i-1) are dereferenceable
// and are not proxy iterators
+---+---+---+---+---+---+---+
| | 1 | 2 | 3 | 4 | 5 | |
+---+---+---+---+---+---+---+
↑ ↑ ↑ ↑
| | | |
rend() | rbegin() end()
| rbegin().base()
begin()
rend().base()
https://riptutorial.com/es/home 433
En la visualización donde los iteradores marcan posiciones entre elementos, la relación es más
simple:
+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 |
+---+---+---+---+---+
↑ ↑
| |
| end()
| rbegin()
begin() rbegin().base()
rend()
rend().base()
Iterador de vectores
Si el objeto vectorial es const , tanto begin como end devuelven un const_iterator . Si desea que se
const_iterator un const_iterator incluso si su vector no es const , puede usar cbegin y cend .
Ejemplo:
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = { 1, 2, 3, 4, 5 }; //intialize vector using an initializer_list
return 0;
}
Salida:
12345
Iterador de mapas
https://riptutorial.com/es/home 434
mymap['c'] = 300;
Salida:
a => 200
b => 100
c => 300
Iteradores de corriente
Los iteradores de flujo son útiles cuando necesitamos leer una secuencia o imprimir datos
formateados desde un contenedor:
// Constructing stream iterators and copying data from stream into vector.
std::copy(
// Iterator which will read stream data as integers.
std::istream_iterator<int>(istr),
// Default constructor produces end-of-stream iterator.
std::istream_iterator<int>(),
std::back_inserter(v));
Un patrón común en otros idiomas es tener una función que produce un "flujo" de objetos, y poder
usar un código de bucle para recorrerlo.
template<class T>
struct generator_iterator {
using difference_type=std::ptrdiff_t;
using value_type=T;
using pointer=T*;
using reference=T;
using iterator_category=std::input_iterator_tag;
std::optional<T> state;
std::function< std::optional<T>() > operation;
// we store the current element in "state" if we have one:
T operator*() const {
https://riptutorial.com/es/home 435
return *state;
}
// to advance, we invoke our operation. If it returns a nullopt
// we have reached the end:
generator_iterator& operator++() {
state = operation();
return *this;
}
generator_iterator operator++(int) {
auto r = *this;
++(*this);
return r;
}
// generator iterators are only equal if they are both in the "end" state:
friend bool operator==( generator_iterator const& lhs, generator_iterator const& rhs ) {
if (!lhs.state && !rhs.state) return true;
return false;
}
friend bool operator!=( generator_iterator const& lhs, generator_iterator const& rhs ) {
return !(lhs==rhs);
}
// We implicitly construct from a std::function with the right signature:
generator_iterator( std::function< std::optional<T>() > f ):operation(std::move(f))
{
if (operation)
state = operation();
}
// default all special member functions:
generator_iterator( generator_iterator && ) =default;
generator_iterator( generator_iterator const& ) =default;
generator_iterator& operator=( generator_iterator && ) =default;
generator_iterator& operator=( generator_iterator const& ) =default;
generator_iterator() =default;
};
ejemplo vivo .
Almacenamos el elemento generado temprano para que podamos detectar más fácilmente si ya
estamos al final.
Como la función de un iterador de generador final nunca se usa, podemos crear un rango de
iteradores de generador copiando solo la std::function . Un iterador de generador construido por
defecto se compara igual a sí mismo y con todos los demás iteradores de generador final.
https://riptutorial.com/es/home 436
Capítulo 72: La Regla De Tres, Cinco Y Cero
Examples
Regla de cinco
C ++ 11
Sin embargo, tenga en cuenta que no seguir la Regla de cinco no suele considerarse un error,
sino una oportunidad de optimización perdida, siempre que se siga la Regla de tres. Si no hay un
constructor de movimientos o un operador de asignación de movimientos disponible cuando el
compilador normalmente usaría uno, en su lugar utilizará la semántica de copia, de ser posible, lo
que resultará en una operación menos eficiente debido a operaciones de copia innecesarias. Si la
semántica de movimiento no se desea para una clase, entonces no es necesario declarar un
constructor de movimiento u operador de asignación.
class Person
{
char* name;
int age;
public:
// Destructor
~Person() { delete [] name; }
https://riptutorial.com/es/home 437
// when the class is used in a container.
Regla de cero
C ++ 11
Podemos combinar los principios de la Regla de cinco y RAII para obtener una interfaz mucho
más sencilla: la Regla de cero: cualquier recurso que deba administrarse debe ser de su propio
tipo. Ese tipo tendría que seguir la Regla de los Cinco, pero todos los usuarios de ese recurso no
necesitan escribir ninguna de las cinco funciones de miembro especiales y simplemente pueden
default todas ellas.
Usando la clase de Person introducida en el ejemplo de la Regla de tres , podemos crear un objeto
de administración de recursos para los cstrings :
class cstring {
private:
https://riptutorial.com/es/home 438
char* p;
public:
~cstring() { delete [] p; }
cstring(cstring const& );
cstring(cstring&& );
cstring& operator=(cstring const& );
cstring& operator=(cstring&& );
Y una vez que esto está separado, nuestra clase Person vuelve mucho más simple:
class Person {
cstring name;
int arg;
public:
~Person() = default;
Person(Person const& ) = default;
Person(Person&& ) = default;
Person& operator=(Person const& ) = default;
Person& operator=(Person&& ) = default;
struct Person {
cstring name;
int arg;
};
Si el cstring fuera un tipo de solo movimiento, con un constructor de delete / copia / operador de
asignación, la Person también se movería automáticamente solo.
Regla de tres
c ++ 03
La Regla de los Tres establece que si un tipo necesita tener un constructor de copia, operador de
asignación de copia o destructor definido por el usuario, entonces debe tener los tres .
El motivo de la regla es que una clase que necesita cualquiera de los tres administra algún
recurso (manejadores de archivos, memoria asignada dinámicamente, etc.), y los tres son
necesarios para administrar ese recurso de manera consistente. Las funciones de copia tratan
sobre cómo el recurso se copia entre los objetos, y el destructor destruiría el recurso, de acuerdo
https://riptutorial.com/es/home 439
con los principios de RAII .
class Person
{
char* name;
int age;
public:
Person(char const* new_name, int new_age)
: name(new char[std::strlen(new_name) + 1])
, age(new_age)
{
std::strcpy(name, new_name);
}
~Person() {
delete [] name;
}
};
Dado que el name fue asignado en el constructor, el destructor lo desasigna para evitar pérdidas
de memoria. ¿Pero qué pasa si se copia ese objeto?
int main()
{
Person p1("foo", 11);
Person p2 = p1;
}
Cuando finalice el main , se llamará a los destructores. El primer destructor de p2 será llamado; se
eliminará la cadena. Entonces se llamará al destructor de p1 . Sin embargo, la cadena ya está
eliminada . Llamar a delete en la memoria que ya se eliminó produce un comportamiento
indefinido.
https://riptutorial.com/es/home 440
}
Protección de autoasignación
Al escribir un operador de asignación de copia, es muy importante que pueda trabajar en caso de
autoasignación. Es decir, tiene que permitir esto:
SomeType t = ...;
t = t;
La autoasignación usualmente no ocurre de una manera tan obvia. Normalmente ocurre a través
de una ruta tortuosa a través de varios sistemas de códigos, donde la ubicación de la asignación
simplemente tiene dos punteros o referencias Person y no tiene idea de que son el mismo objeto.
Cualquier operador de asignación de copia que escriba debe poder tener esto en cuenta.
La forma típica de hacerlo es envolver toda la lógica de asignación en una condición como esta:
Como ejemplo, la técnica normal para implementar el operador de asignación es el copy and swap
idiom . La implementación normal de esta técnica no se molesta en probar la autoasignación
(aunque la autoasignación es costosa porque se hace una copia). La razón es que la
pesimización del caso normal ha demostrado ser mucho más costosa (ya que ocurre con más
https://riptutorial.com/es/home 441
frecuencia).
c ++ 11
https://riptutorial.com/es/home 442
Capítulo 73: Lambdas
Sintaxis
• [ captura por defecto , lista de captura ] ( lista de argumentos ) atributos de especificación de
lanzamiento mutable -> return-type { lambda-body } // Orden de los especificadores y
atributos de lambda.
• [ lista de captura ] ( lista de argumentos ) { lambda-cuerpo } // Definición común de lambda.
• [=] ( argument-list ) { lambda-body } // Captura todas las variables locales necesarias por
valor.
• [&] ( argument-list ) { lambda-body } // Captura todas las variables locales necesarias por
referencia.
• [ capture-list ] { lambda-body } // Se pueden omitir la lista de argumentos y los
especificadores.
Parámetros
Parámetro Detalles
lista de
Especifica los argumentos de la función lambda.
argumentos
https://riptutorial.com/es/home 443
Parámetro Detalles
Observaciones
C ++ 17 (el borrador actual) introduce constexpr lambdas, básicamente lambdas que pueden
evaluarse en tiempo de compilación. Un lambda es automáticamente constexpr si satisface los
requisitos de constexpr , pero también puede especificarlo usando la palabra clave constexpr :
Examples
¿Qué es una expresión lambda?
Una expresión lambda proporciona una forma concisa de crear objetos de funciones simples.
Una expresión lambda es un prvalue cuyo objeto de resultado se llama objeto de cierre , que se
comporta como un objeto de función.
Una expresión lambda se usa a menudo como un argumento para funciones que toman un objeto
llamable. Eso puede ser más simple que crear una función nombrada, que solo se usaría cuando
se pase como argumento. En tales casos, las expresiones lambda generalmente se prefieren
porque permiten definir los objetos de función en línea.
Una lambda consta normalmente de tres partes: una lista de captura [] , una lista de parámetros
opcional () y un cuerpo {} , todos los cuales pueden estar vacíos:
Lista de captura
[] es la lista de captura . Por defecto, no se puede acceder a las variables del ámbito adjunto
https://riptutorial.com/es/home 444
por un lambda. Capturar una variable lo hace accesible dentro de la lambda, ya sea como una
copia o como una referencia . Las variables capturadas se convierten en parte de la lambda; en
contraste con los argumentos de la función, no se tienen que pasar al llamar a lambda.
Lista de parámetros
() es la lista de parámetros , que es casi la misma que en las funciones normales. Si la lambda
no toma argumentos, estos paréntesis se pueden omitir (excepto si necesita declarar la lambda
mutable ). Estas dos lambdas son equivalentes:
C ++ 14
La lista de parámetros puede usar el tipo de marcador de posición auto lugar de los tipos reales.
Al hacerlo, este argumento se comporta como un parámetro de plantilla de una plantilla de
función. Las siguientes lambdas son equivalentes cuando desea ordenar un vector en código
genérico:
Cuerpo de funcion
Llamando a un lambda
El objeto de resultado de una expresión lambda es un cierre , que se puede llamar usando el
operator() (como con otros objetos de función):
int multiplier = 5;
auto timesFive = [multiplier](int a) { return a * multiplier; };
std::out << timesFive(2); // Prints 10
multiplier = 15;
std::out << timesFive(2); // Still prints 2*5 == 10
https://riptutorial.com/es/home 445
Tipo de retorno
Lambda mutable
Los objetos capturados por valor en la lambda son, por defecto, inmutables. Esto se debe a que el
operator() del objeto de cierre generado es const de forma predeterminada.
auto func = [c = 0](){++c; std::cout << c;}; // fails to compile because ++c
// tries to mutate the state of
// the lambda.
Se puede permitir la modificación utilizando la palabra clave mutable , que hace que el operator()
del objeto más cercano no sea const :
auto func = [c = 0]() mutable -> int {++c; std::cout << c; return c;};
Antes de C ++ 11:
C ++ 11
// Declare a vector
const int arr[] = { 1, 2, 3, 4, 5 };
https://riptutorial.com/es/home 446
std::vector<int> vec(arr, arr+5);
// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
std::vector<int>::iterator it = std::find_if(vec.begin(), vec.end(), islessthan(threshold));
Desde C ++ 11:
C ++ 11
// Declare a vector
std::vector<int> vec{ 1, 2, 3, 4, 5 };
// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
auto it = std::find_if(vec.begin(), vec.end(), [threshold](int value) { return value <
threshold; });
Para las lambdas con una sola declaración de retorno, o múltiples declaraciones de retorno cuyas
expresiones son del mismo tipo, el compilador puede deducir el tipo de retorno:
// Returns bool, because "value > 10" is a comparison which yields a Boolean result
auto l = [](int value) {
return value > 10;
}
Para las lambdas con múltiples declaraciones de retorno de diferentes tipos, el compilador no
puede deducir el tipo de devolución:
// error: return types must match if lambda has unspecified return type
auto l = [](int value) {
if (value < 10) {
return 1;
} else {
return 1.5;
}
};
Las reglas para esto coinciden con las reglas para auto deducción de auto tipo. Lambdas sin tipos
de retorno explícitamente especificados nunca devuelve referencias, por lo que si se desea un
https://riptutorial.com/es/home 447
tipo de referencia, también debe especificarse explícitamente:
Si especifica el nombre de la variable en la lista de captura, lambda la capturará por valor. Esto
significa que el tipo de cierre generado para la lambda almacena una copia de la variable. Esto
también requiere que el tipo de variable sea construible por copia :
int a = 0;
[a]() {
return a; // Ok, 'a' is captured by value
};
C ++ 14
auto p = std::unique_ptr<T>(...);
A partir de C ++ 14, es posible inicializar variables en el lugar. Esto permite mover solo tipos para
ser capturados en la lambda.
C ++ 14
auto p = std::make_unique<T>(...);
[p = std::move(p)]() {
return p->createWidget();
};
Aunque una lambda captura variables por valor cuando se les da su nombre, tales variables no
pueden modificarse dentro del cuerpo lambda por defecto. Esto se debe a que el tipo de cierre
coloca el cuerpo lambda en una declaración de operator() const .
La const aplica a los accesos a las variables miembro del tipo de cierre, y las variables capturadas
que son miembros del cierre (todas las apariencias de lo contrario):
int a = 0;
[a]() {
a = 2; // Illegal, 'a' is accessed via `const`
decltype(a) a1 = 1;
a1 = 2; // valid: variable 'a1' is not const
};
https://riptutorial.com/es/home 448
Para eliminar la const , debe especificar la palabra clave mutable en la lambda:
int a = 0;
[a]() mutable {
a = 2; // OK, 'a' can be modified
return a;
};
Debido a que a se capturó por valor, cualquier modificación realizada al llamar a la lambda no
afectará a . El valor de a se copió en la lambda cuando se construyó, por lo que la copia de a de la
lambda está separada de la variable externa a .
int a = 5 ;
auto plus5Val = [a] (void) { return a + 5 ; } ;
auto plus5Ref = [&a] (void) {return a + 5 ; } ;
a = 7 ;
std::cout << a << ", value " << plus5Val() << ", reference " << plus5Ref() ;
// The result will be "7, value 10, reference 12"
Captura generalizada
C ++ 14
Lambdas puede capturar expresiones, en lugar de solo variables. Esto permite que las lambdas
almacenen tipos de solo movimiento:
auto p = std::make_unique<T>(...);
Esto mueve la variable p externa a la variable de captura lambda, también llamada p . lamb ahora
posee la memoria asignada por make_unique . Debido a que el cierre contiene un tipo que no se
puede copiar, esto significa que el lamb no puede copiarse. Pero se puede mover:
Tenga en cuenta que std::function<> requiere que los valores almacenados sean copiables.
Puedes escribir tu propia std::function , que requiere solo movimiento , o simplemente puedes
meter el lambda en un contenedor shared_ptr :
https://riptutorial.com/es/home 449
(auto&&...args)->decltype(auto) {
return (*spf)(decltype(args)(args)...);
};
};
auto lamb_shared = shared_lambda(std::move(lamb_move));
toma nuestro lambda de solo movimiento y rellena su estado en un puntero compartido y luego
devuelve un lambda que se puede copiar y luego almacenar en una std::function o similar.
La captura generalizada utiliza auto deducción de tipo auto para el tipo de variable. Declarará
estas capturas como valores por defecto, pero también pueden ser referencias:
int a = 0;
auto lamb = [&v = a](int add) //Note that `a` and `v` have different names
{
v += add; //Modifies `a`
};
Generalize capture no necesita capturar una variable externa en absoluto. Puede capturar una
expresión arbitraria:
Esto es útil para dar a los lambdas valores arbitrarios que pueden mantener y potencialmente
modificar, sin tener que declararlos externamente a la lambda. Por supuesto, eso solo es útil si no
tiene la intención de acceder a esas variables después de que la lambda haya completado su
trabajo.
Si precede el nombre de una variable local con un & , entonces la variable se capturará por
referencia. Conceptualmente, esto significa que el tipo de cierre de la lambda tendrá una variable
de referencia, inicializada como una referencia a la variable correspondiente desde fuera del
alcance de la lambda. Cualquier uso de la variable en el cuerpo lambda se referirá a la variable
original:
set();
https://riptutorial.com/es/home 450
assert(a == 1);
Por supuesto, capturar por referencia significa que la lambda no debe escapar al alcance de las
variables que captura. Por lo tanto, puede llamar a funciones que toman una función, pero no
debe llamar a una función que almacenará el lambda más allá del alcance de sus referencias. Y
no debes devolver la lambda.
int a = 1;
int b = 2;
La captura explícita todavía se puede hacer junto con la captura implícita por defecto. La
definición de captura explícita anulará la captura predeterminada:
int a = 0;
int b = 1;
[=, &b]() {
a = 2; // Illegal; 'a' is capture by value, and lambda is not 'mutable'
b = 2; // OK; 'b' is captured by reference
};
Lambdas genericas
c ++ 14
Las funciones Lambda pueden tomar argumentos de tipos arbitrarios. Esto permite que un
lambda sea más genérico:
int i = twice(2); // i == 4
std::string s = twice("hello"); // s == "hellohello"
https://riptutorial.com/es/home 451
struct _unique_lambda_type
{
template<typename T>
auto operator() (T x) const {return x + x;}
};
Aquí, x se deduce en función del primer argumento de la función, mientras que y siempre será int
.
Las lambdas genéricas también pueden tomar argumentos por referencia, usando las reglas
habituales para auto y & . Si un parámetro genérico se toma como auto&& , esta es una referencia
de reenvío al argumento pasado y no una referencia de rvalue :
Las funciones Lambda pueden ser variadas y perfectamente remiten sus argumentos:
o:
Una razón importante para usar lambdas genéricas es para visitar la sintaxis.
Aquí estamos visitando de manera polimórfica; pero en otros contextos, los nombres del tipo que
estamos pasando no son interesantes:
mutex_wrapped<std::ostream&> os = std::cout;
os.write([&](auto&& os){
os << "hello world\n";
});
Repitiendo el tipo de std::ostream& is noise here; Sería como tener que mencionar el tipo de
variable cada vez que la uses. Aquí estamos creando un visitante, pero no polimórfico; auto se
https://riptutorial.com/es/home 452
utiliza por el mismo motivo por el que puede usar auto en un bucle for(:) .
Si la lista de capturas de una lambda está vacía, entonces la lambda tiene una conversión
implícita a un puntero de función que toma los mismos argumentos y devuelve el mismo tipo de
retorno:
auto sorter = [](int lhs, int rhs) -> bool {return lhs < rhs;};
Dicha conversión también se puede hacer cumplir con el operador unario plus:
Esta característica es principalmente útil para usar lambdas con API que tratan con punteros de
función, en lugar de objetos de función C ++.
C ++ 14
La conversión a un puntero de función también es posible para las lambdas genéricas con una
lista de captura vacía. Si es necesario, la deducción de argumentos de la plantilla se utilizará para
seleccionar la especialización correcta.
auto sorter = [](auto lhs, auto rhs) { return lhs < rhs; };
using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter; // deduces int, int
// note however that the following is ambiguous
// func_ptr sorter_func2 = +sorter;
Una expresión lambda evaluada en la función miembro de una clase es implícitamente un amigo
de esa clase:
class Foo
{
private:
int i;
public:
Foo(int val) : i(val) {}
https://riptutorial.com/es/home 453
{
auto lamb = [](Foo &foo, int val)
{
// modification of a private member variable
foo.i = val;
};
Tal lambda no es solo un amigo de esa clase, sino que tiene el mismo acceso que la clase en la
que está declarada.
Lambdas puede capturar this puntero que representa la instancia de objeto en la que se activó la
función externa. Esto se hace agregando this a la lista de captura:
class Foo
{
private:
int i;
public:
Foo(int val) : i(val) {}
void Test()
{
// capture the this pointer by value
auto lamb = [this](int val)
{
i = val;
};
lamb(30);
}
};
Cuando this es capturado, la lambda puede utilizar nombres de los miembros de su clase que
contiene como si se tratara de su clase que contiene. Así que un implícito this-> se aplica a tales
miembros.
Tenga en cuenta que this se captura por valor, pero no por el valor del tipo. Es capturado por el
valor de this , que es un puntero . Como tal, la lambda no posee this . Si el lambda out vive el
tiempo de vida del objeto que lo creó, el lambda puede dejar de ser válido.
Esto también significa que la lambda puede modificar this sin ser declarado mutable . Es el
puntero el que es const , no el objeto al que se apunta. Es decir, a menos que la función miembro
externa fuera en sí misma una función const .
Además, tenga en cuenta que las cláusulas de captura predeterminadas, tanto [=] como [&] ,
también capturarán this implícitamente. Y ambos lo capturan por el valor del puntero. De hecho,
es un error especificar this en la lista de captura cuando se da un valor predeterminado.
C ++ 17
https://riptutorial.com/es/home 454
Lambdas puede capturar una copia de this objeto, creado en el momento en que se crea la
lambda. Esto se hace agregando *this a la lista de captura:
class Foo
{
private:
int i;
public:
Foo(int val) : i(val) {}
void Test()
{
// capture a copy of the object given by the this pointer
auto lamb = [*this](int val) mutable
{
i = val;
};
Las funciones de Lambda en C ++ son azúcar sintáctica que proporcionan una sintaxis muy
concisa para escribir functores . Como tal, se puede obtener una funcionalidad equivalente en C
++ 03 (aunque mucho más detallado) al convertir la función lambda en un functor:
public:
// Constructor
inline Functor(T1 val, T2& ref) : val(val), ref(ref) {}
// Functor body
R operator()(int arg1, int arg2) const {
https://riptutorial.com/es/home 455
/* lambda-body */
return R();
}
};
Si la función lambda es mutable entonces haga que el operador de llamadas del funtor no sea
constante, es decir:
Lambdas recursivas
Digamos que deseamos escribir el gcd() de Euclid gcd() como un lambda. Como una función, es:
Pero un lambda no puede ser recursivo, no tiene forma de invocarse. Un lambda no tiene nombre
y usar this dentro del cuerpo de un lambda se refiere a un this capturado (asumiendo que el
lambda se crea en el cuerpo de una función miembro, de lo contrario es un error). Entonces,
¿cómo resolvemos este problema?
Usa std::function
Podemos tener una captura lambda de una referencia a una std::function aún no construida:
Esto funciona, pero debe usarse con moderación. Es lento (estamos usando el borrado de tipo
ahora en lugar de una llamada de función directa), es frágil (copiar gcd o devolver gcd se romperá
ya que la lambda se refiere al objeto original), y no funcionará con lambdas genéricos.
https://riptutorial.com/es/home 456
Utilizando dos punteros inteligentes:
Esto agrega mucha indirección (que es una sobrecarga), pero se puede copiar / devolver, y todas
las copias comparten el estado. Le permite devolver el lambda y, por lo demás, es menos frágil
que la solución anterior.
Usa un combinador en Y
Con la ayuda de una estructura de utilidad corta, podemos resolver todos estos problemas:
// a forwarding operator():
template <class... Args>
decltype(auto) operator()(Args&&... args) const {
// we pass ourselves to f, then the arguments.
// the lambda should take the first argument as `auto&& recurse` or similar.
return f(*this, std::forward<Args>(args)...);
}
};
// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
return {std::forward<F>(f)};
}
// (Be aware that in C++17 we can do better than a `make_` function)
El y_combinator es un concepto del cálculo lambda que te permite tener una recursión sin poder
y_combinator hasta que estés definido. Este es exactamente el problema que tienen las lambdas.
Crea un lambda que toma "recurse" como su primer argumento. Cuando quieres recursionar,
pasas los argumentos para recursionar.
El y_combinator luego devuelve un objeto de función que llama a esa función con sus argumentos,
pero con un objeto "recurse" adecuado (es decir, el propio y_combinator ) como su primer
argumento. Reenvía el resto de los argumentos con los que llama al y_combinator a la lambda
https://riptutorial.com/es/home 457
también.
En breve:
C ++ 14
template<std::size_t...Is>
void print_indexes( std::index_sequence<Is...> ) {
using discard=int[];
(void)discard{0,((void)(
std::cout << Is << '\n' // here Is is a compile-time constant.
),0)...};
}
template<std::size_t I>
void print_indexes_upto() {
return print_indexes( std::make_index_sequence<I>{} );
}
template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};
template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f){
using discard=int[];
(void)discard{0,(void(
f( index<Is> )
),0)...};
};
https://riptutorial.com/es/home 458
}
template<std::size_t N>
auto index_over(index_t<N> = {}) {
return index_over( std::make_index_sequence<N>{} );
}
C ++ 17
template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f){
((void)(f(index<Is>)), ...);
};
}
Una vez que haya hecho eso, puede usar esto para reemplazar tener que desempaquetar
manualmente los paquetes de parámetros con una segunda sobrecarga en otro código,
permitiéndole desempaquetar los paquetes de parámetros "en línea":
from_zero_to_N(
[&](auto i){
using std::get;
f( get<i>( std::forward<Tup>(tup) ) );
}
);
}
template<std::size_t I>
void print_indexes_upto() {
index_over(index<I>)([](auto i){
std::cout << i << '\n'; // here i is a compile-time constant
});
}
https://riptutorial.com/es/home 459
Lea Lambdas en línea: https://riptutorial.com/es/cplusplus/topic/572/lambdas
https://riptutorial.com/es/home 460
Capítulo 74: Literales
Introducción
Tradicionalmente, un literal es una expresión que denota una constante cuyo tipo y valor son
evidentes a partir de su ortografía. Por ejemplo, 42 es un literal, mientras que x no lo es, ya que
uno debe ver su declaración para conocer su tipo y leer las líneas de código anteriores para
conocer su valor.
Sin embargo, C ++ 11 también agregó literales definidos por el usuario , que no son literales en el
sentido tradicional, pero se pueden usar como una abreviatura para llamadas a funciones.
Examples
cierto
Una palabra clave que denota uno de los dos valores posibles de tipo bool .
bool ok = true;
if (!f()) {
ok = false;
goto end;
}
falso
Una palabra clave que denota uno de los dos valores posibles de tipo bool .
bool ok = true;
if (!f()) {
ok = false;
goto end;
}
nullptr
C ++ 11
Una palabra clave que denota una constante de puntero nula. Se puede convertir a cualquier tipo
de puntero o puntero a miembro, produciendo un puntero nulo del tipo resultante.
https://riptutorial.com/es/home 461
void f(int* p);
int main() {
f(nullptr); // ok
g(nullptr); // error
h(nullptr); // ok
}
esta
Dentro de una función miembro de una clase, la palabra clave de this es un puntero a la instancia
de la clase en la que se llama a la función. this no se puede utilizar en una función miembro
estática.
struct S {
int x;
S& operator=(const S& other) {
x = other.x;
// return a reference to the object being assigned to
return *this;
}
};
C ++ 11
this también se puede usar en un inicializador de paréntesis o igual para un miembro de datos no
estáticos.
struct S;
struct T {
T(const S* s);
// ...
};
struct S {
// ...
T t{this};
};
Literal entero
https://riptutorial.com/es/home 462
• decimal-literal
Es un dígito decimal distinto de cero (1, 2, 3, 4, 5, 6, 7, 8, 9), seguido de cero o más dígitos
decimales (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
int d = 42;
• octal-literal
int o = 052
• hex-literal
El sufijo entero, si se proporciona, puede contener uno o ambos de los siguientes (si se
proporcionan ambos, pueden aparecer en cualquier orden:
• sufijo largo (el carácter l o el carácter L) o el sufijo largo-largo (la secuencia de caracteres ll
o la secuencia de caracteres LL) (desde C ++ 11)
Notas
Las letras en los literales enteros no distinguen entre mayúsculas y minúsculas: 0xDeAdBaBeU y
0XdeadBABEu representan el mismo número (una excepción es el sufijo long-long, que es ll o LL,
nunca lL o Ll)
No hay literales enteros negativos. Expresiones como -1 aplican el operador menos unario al
valor representado por el literal, que puede implicar conversiones de tipo implícitas.
https://riptutorial.com/es/home 463
En C antes de C99 (pero no en C ++), se permite que los valores decimales sin sufijo que no
caben en long int tengan el tipo unsigned long int.
Cuando se usa en una expresión de control de #if o #elif, todas las constantes enteras con signo
actúan como si tuvieran el tipo std :: intmax_t y todas las constantes enteras sin signo actúen
como si tuvieran el tipo std :: uintmax_t.
https://riptutorial.com/es/home 464
Capítulo 75: Literales definidos por el usuario
Examples
Literales definidos por el usuario con valores dobles largos.
#include <iostream>
int main()
{
std::cout << "3 km = " << 3.0_km << " m\n";
std::cout << "3 mi = " << 3.0_mi << " m\n";
return 0;
}
3 km = 3000 m
3 mi = 4828.03 m
C ++ 14
#include <chrono>
#include <iostream>
int main()
{
using namespace std::literals::chrono_literals;
std::chrono::nanoseconds t1 = 600ns;
std::chrono::microseconds t2 = 42us;
std::chrono::milliseconds t3 = 51ms;
std::chrono::seconds t4 = 61s;
https://riptutorial.com/es/home 465
std::chrono::minutes t5 = 88min;
auto t6 = 2 * 0.5h;
std::cout.precision(13);
std::cout << total.count() << " nanoseconds" << std::endl; // 8941051042600 nanoseconds
std::cout << std::chrono::duration_cast<std::chrono::hours>(total).count()
<< " hours" << std::endl; // 2 hours
}
C ++ 14
#include <codecvt>
#include <iostream>
#include <locale>
#include <string>
int main()
{
using namespace std::literals::string_literals;
Nota:
https://riptutorial.com/es/home 466
C ++ 14
#include <complex>
#include <iostream>
int main()
{
using namespace std::literals::complex_literals;
std::cout << "abs" << c << " = " << abs(c) << std::endl; // abs(2,1) = 2.23607
std::cout << "abs" << cf << " = " << abs(cf) << std::endl; // abs(2,1) = 2.23607
std::cout << "abs" << cl << " = " << abs(cl) << std::endl; // abs(2,1) = 2.23607
}
Aquí viene un ejemplo famoso con una implementación hecha a sí misma para números binarios:
#include <iostream>
int main()
{
std::cout << 10101_B << ", " << 011011000111_b << '\n' ; // prints 21, 1735
https://riptutorial.com/es/home 467
}
https://riptutorial.com/es/home 468
Capítulo 76: Manipulación de bits
Observaciones
Para usar std::bitset , deberá incluir el encabezado <bitset> .
#include <bitset>
std::bitset sobrecarga todas las funciones del operador para permitir el mismo uso que el manejo
en estilo c de los bitsets.
Referencias
Examples
Poniendo un poco
std::bitset<5> num(std::string("01100"));
num.set(0); // num is now 01101
num.set(2); // num is still 01101
num.set(4,true); // num is now 11110
Despejando un poco
https://riptutorial.com/es/home 469
// Bit x will be cleared
number &= ~(1LL << x);
std::bitset<5> num(std::string("01100"));
num.reset(2); // num is now 01000
num.reset(0); // num is still 01000
num.set(3,false); // num is now 00000
Toggling un poco
Revisando un poco
(number >> x) & 1LL; // 1 if the 'x'th bit of 'number' is set, 0 otherwise
https://riptutorial.com/es/home 470
(number & (1LL << x)); // (1 << x) if the 'x'th bit of 'number' is set, 0 otherwise
Cualquiera de los dos puede usarse como condicional, ya que todos los valores distintos de cero
se consideran verdaderos.
std::bitset<5> num(std::string("00100"));
num.set(0,true); // num is now 00101
num.set(2,false); // num is now 00001
(Vea aquí una explicación de por qué esto funciona y en realidad es el mejor enfoque).
https://riptutorial.com/es/home 471
Manipulación de bits estilo C
template <typename T>
T rightmostSetBitRemoved(T n)
{
// static_assert(std::is_integral<T>::value && !std::is_signed<T>::value, "type should be
unsigned"); // For c++11 and later
return n & (n - 1);
}
Explicación
Un buen truco (basado en Eliminar el bit del conjunto más a la derecha ) es:
Pasa por tantas iteraciones como bits establecidos, por lo que es bueno cuando se espera que el
value tenga pocos bits distintos de cero.
El método fue propuesto por primera vez por Peter Wegner (en CACM 3/322 - 1960) y es bien
conocido ya que aparece en lenguaje de programación C por Brian W. Kernighan y Dennis M.
Ritchie.
Esto requiere 12 operaciones aritméticas, una de las cuales es una combinación múltiple:
unsigned popcount(std::uint64_t x)
{
const std::uint64_t m1 = 0x5555555555555555; // binary: 0101...
https://riptutorial.com/es/home 472
const std::uint64_t m2 = 0x3333333333333333; // binary: 00110011..
const std::uint64_t m4 = 0x0f0f0f0f0f0f0f0f; // binary: 0000111100001111
x -= (x >> 1) & m1; // put count of each 2 bits into those 2 bits
x = (x & m2) + ((x >> 2) & m2); // put count of each 4 bits into those 4 bits
x = (x + (x >> 4)) & m4; // put count of each 8 bits into those 8 bits
return (x * h01) >> 56; // left 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ...
}
Este tipo de implementación tiene el mejor comportamiento en el peor de los casos (ver el peso
de Hamming para más detalles).
Muchas CPU tienen una instrucción específica (como el popcnt de x86) y el compilador podría
ofrecer una función integrada específica ( no estándar ). Por ejemplo, con g ++ hay:
El n & (n - 1) truco (ver Eliminar el bit del conjunto más a la derecha ) también es útil para
determinar si un entero es una potencia de 2:
Una de las varias aplicaciones de la manipulación de bits es convertir una letra de pequeña a
mayúscula o viceversa al elegir una máscara y una operación de bit adecuada. Por ejemplo, la
carta tiene esta representación binaria 01(1)00001 mientras que su contraparte capital tiene
01(0)00001 . Difieren únicamente en el bit entre paréntesis. En este caso, convertir una letra de
pequeña a mayúscula es básicamente establecer el bit entre paréntesis en uno. Para ello,
hacemos lo siguiente:
/****************************************
convert small letter to captial letter.
========================================
a: 01100001
mask: 11011111 <-- (0xDF) 11(0)11111
:---------
a&mask: 01000001 <-- A letter
*****************************************/
#include <cstdio>
https://riptutorial.com/es/home 473
int main()
{
char op1 = 'a'; // "a" letter (i.e. small case)
int mask = 0xDF; // choosing a proper mask
return 0;
}
El resultado es
https://riptutorial.com/es/home 474
Capítulo 77: Manipuladores de corriente
Introducción
Los manipuladores son funciones auxiliares especiales que ayudan a controlar los flujos de
entrada y salida utilizando el operator >> o el operator << .
Observaciones
Los manipuladores se pueden utilizar de otra manera. Por ejemplo:
str.setf(base == 8 ? std::ios_base::oct :
base == 10 ? std::ios_base::dec :
base == 16 ? std::ios_base::hex :
std::ios_base::fmtflags(0),
std::ios_base::basefield);
6. std::ios_base::basefield .
Para flag s: dec , hex y oct :
https://riptutorial.com/es/home 475
• os.setf(std::ios_base::flag, std::ios_base::basefield); es igual a os << std::flag;
is.setf(std::ios_base::flag, std::ios_base::basefield); es igual a is >> std::flag;
(1)
• str.unsetf(std::ios_base::flag, std::ios_base::basefield); es igual a
str.setf(std::ios_base::fmtflags(0), std::ios_base::basefield);
(2)
7. std::ios_base::adjustfield .
Para flag s: left , right e internal :
8. std::ios_base::floatfield .
Examples
https://riptutorial.com/es/home 476
Manipuladores de corriente
bool boolValue;
std::cin >> std::boolalpha >> boolValue;
std::cout << "Value \"" << std::boolalpha << boolValue
<< "\" was parsed as " << std::noboolalpha << boolValue;
// Input: true
// Output: Value "true" was parsed as 0
std::dec (decimal), std::hex (hexadecimal) y std::oct (octal) - se usan para cambiar la base de
enteros.
#include <sstream>
Si desea ver más información sobre std::istringstream consulte el encabezado < sstream >.
https://riptutorial.com/es/home 477
std::setw(n) : cambia el ancho del siguiente campo de entrada / salida a n exactamente.
La propiedad de ancho n se restablece a 0 cuando se llaman algunas funciones (la lista completa
está aquí ).
// Output: 51
// setw(7): 51
// setw(7), more output: 13*****67 94
#include <locale>
...
std::cout.imbue(std::locale("en_US.utf8"));
https://riptutorial.com/es/home 478
<< "usd: " << std::setw(15) << std::put_money(367, true) << '\n'
<< "usd: " << std::setw(15)
<< std::setfill(' ') << std::put_money(367, true) << '\n';
// Output:
// flt: -**********9.87
// hex: *************41
// $: $3.67**********
// usd: USD *******3.67
// usd: USD 3.67
fmtflags
#include <sstream>
...
double f;
std::istringstream is("0x1P-1022");
double f = std::strtod(is.str().c_str(), NULL);
std::cout << "Parsing 0x1P-1022 as hex gives " << f << '\n';
// Output:
// The number 0.01 in fixed: 0.070000
// The number 0.01 in scientific: 7.000000e-02
// The number 0.01 in hexfloat: 0x1.1eb851eb851ecp-4
// The number 0.01 in default: 0.07
https://riptutorial.com/es/home 479
// Parsing 0x1P-1022 as hex gives 2.22507e-308
double f;
std::istringstream("0x1P-1022") >> std::hexfloat >> f;
std::cout << "Parsing 0x1P-1022 as hex gives " << f << '\n';
// Output: Parsing 0x1P-1022 as hex gives 0
std::cout << "7.0 with showpoint: " << std::showpoint << 7.0 << '\n'
<< "7.0 with noshowpoint: " << std::noshowpoint << 7.0 << '\n';
// Output: 1.0 with showpoint: 7.00000
// 1.0 with noshowpoint: 7
Predeterminado if std::noshowpos .
https://riptutorial.com/es/home 480
std::ios_base::fmtflags(0) . Significa salida decimal y entrada dependiente del prefijo.
#include <cmath>
#include <limits>
...
// Output:
// default precision (6): pi: 3.14159
// 10pi: 31.4159
// std::setprecision(4): 10pi: 31.42
// 10000pi: 3.142e+04
// std::fixed: 10000pi: 31415.9265
// std::setprecision(10): pi: 3.141592654
// max-1 radix precicion: pi:
3.14159265358979323851280895940618620443274267017841339111328125
// max+1 radix precision: pi:
3.14159265358979323851280895940618620443274267017841339111328125
// significant digits prec: pi: 3.14159265358979324
#include <sstream>
...
https://riptutorial.com/es/home 481
std::cout << "Parsing \"10 010\" with std::oct gives: " << num1 << ' ' << num2 << '\n';
// Output: Parsing "10 010" with std::oct gives: 8 8
#include <sstream>
...
std::quoted(s[, delim[, escape]]) [C ++ 14]: inserta o extrae cadenas citadas con espacios
incrustados.
#include <sstream>
...
std::stringstream ss;
std::string in = "String with spaces, and embedded \"quotes\" too";
std::string out;
ss << std::quoted(in);
std::cout << "read in [" << in << "]\n"
<< "stored as [" << ss.str() << "]\n";
ss >> std::quoted(out);
https://riptutorial.com/es/home 482
std::cout << "written out [" << out << "]\n";
// Output:
// read in [String with spaces, and embedded "quotes" too]
// stored as ["String with spaces, and embedded \"quotes\" too"]
// written out [String with spaces, and embedded "quotes" too]
std::ends: inserta un carácter nulo '\0' en el flujo de salida. Más formalmente la declaración de
este manipulador parece
y este manipulador coloca el carácter llamando a os.put(charT()) cuando se usa en una expresión
os << std::ends;
std::endly std::flush ambos out.flush() flujo de out llamando a out.flush() . Provoca producción
inmediata. Pero std::endl inserta el std::endl final de línea '\n' antes de vaciar.
std::cout << "First line." << std::endl << "Second line. " << std::flush
<< "Still second line.";
// Output: First line.
// Second line. Still second line.
std::cout << "\nDefault fill: " << std::setw(10) << 79 << '\n'
<< "setfill('#'): " << std::setfill('#')
<< std::setw(10) << 42 << '\n';
// Output:
// Default fill: 79
// setfill('#'): ########79
https://riptutorial.com/es/home 483
std::cout.imbue(std::locale("en_US.utf8"));
std::cout << std::showbase << "en_US: " << std::put_money(money)
<< " or " << std::put_money(money, true) << '\n';
// Output: en_US: $1.23 or USD 1.23
std::cout.imbue(std::locale("ru_RU.utf8"));
std::cout << "ru_RU: " << std::put_money(money)
<< " or " << std::put_money(money, true) << '\n';
// Output: ru_RU: 1.23 руб or 1.23 RUB
std::cout.imbue(std::locale("ja_JP.utf8"));
std::cout << "ja_JP: " << std::put_money(money)
<< " or " << std::put_money(money, true) << '\n';
// Output: ja_JP: 123 or JPY 123
std::put_time(tmb, fmt) [C ++ 11]: formatea y genera un valor de fecha / hora en std::tm acuerdo
con el formato especificado fmt .
tmb : puntero a la estructura horaria del calendario const std::tm* según se obtiene de localtime()
o gmtime() .
fmt : puntero a una cadena de caracteres terminada en const CharT* especifica el formato de
conversión.
#include <ctime>
...
std::time_t t = std::time(nullptr);
std::tm tm = *std::localtime(&t);
std::cout.imbue(std::locale("ru_RU.utf8"));
std::cout << "\nru_RU: " << std::put_time(&tm, "%c %Z") << '\n';
// Possible output:
// ru_RU: Вт 04 июл 2017 15:08:35 UTC
#include <sstream>
...
std::string str;
std::istringstream(" \v\n\r\t Wow!There is no whitespaces!") >> std::ws >> str;
std::cout << str;
// Output: Wow!There is no whitespaces!
https://riptutorial.com/es/home 484
std::money_get del entorno local imbuido actualmente in , y almacena el valor en mon (de long
double o std::basic_string type). El manipulador espera las cadenas de moneda internacionales
requeridas si intl es true , de lo contrario espera símbolos de moneda opcionales .
#include <sstream>
#include <locale>
...
in.imbue(std::locale("en_US.UTF-8"));
in >> std::get_money(v1) >> std::get_money(v2) >> std::get_money(v3, true);
if (in) {
std::cout << std::quoted(in.str()) << " parsed as: "
<< v1 << ", " << v2 << ", " << v3 << '\n';
}
// Output:
// "$1,234.56 2.22 USD 3.33" parsed as: 123456, 222, 333
std::get_time(tmb, fmt)[C ++ 11] - analiza un valor de fecha / hora almacenado en tmb del
formato especificado fmt .
#include <sstream>
#include <locale>
...
std::tm t = {};
std::istringstream ss("2011-Februar-18 23:12:34");
ss.imbue(std::locale("de_DE.utf-8"));
ss >> std::get_time(&t, "%Y-%b-%d %H:%M:%S");
if (ss.fail()) {
std::cout << "Parse failed\n";
}
else {
std::cout << std::put_time(&t, "%c") << '\n';
}
// Possible output:
// Sun Feb 18 23:12:34 2011
https://riptutorial.com/es/home 485
Capítulo 78: Más comportamientos
indefinidos en C ++
Introducción
Más ejemplos de cómo C ++ puede salir mal.
Examples
Refiriéndose a los miembros no estáticos en las listas de inicializadores
Ejemplo
struct W { int j; };
struct X : public virtual W { };
struct Y {
int *p;
X x;
Y() : p(&x.j) { // undefined, x is not yet constructed
}
};
https://riptutorial.com/es/home 486
Capítulo 79: Mejoramiento
Introducción
Al compilar, el compilador a menudo modificará el programa para aumentar el rendimiento. Esto
está permitido por la regla "como si" , que permite cualquier y todas las transformaciones que no
cambian el comportamiento observable.
Examples
Expansión en línea / en línea
La expansión en línea (también conocida como en línea) es la optimización del compilador que
reemplaza una llamada a una función con el cuerpo de esa función. Esto ahorra la sobrecarga de
llamadas a la función, pero a costa de espacio, ya que la función puede duplicarse varias veces.
// source:
int foo(int a)
{
return process(a);
}
int foo(int a)
{
return 2 * a; // the body of process() is copied into foo()
}
Se requiere que el tamaño de cualquier objeto o subobjeto miembro sea al menos 1, incluso si el
tipo es un tipo de class vacío (es decir, una class o struct que no tiene miembros de datos no
estáticos) para poder garantizar que Las direcciones de objetos distintos del mismo tipo son
siempre distintas.
Sin embargo, class subobjetos de la class base no están tan restringidos, y pueden optimizarse
completamente desde el diseño del objeto:
#include <cassert>
https://riptutorial.com/es/home 487
struct Base {}; // empty class
int main() {
// the size of any object of empty class type is at least 1
assert(sizeof(Base) == 1);
Referencia: cppreference
https://riptutorial.com/es/home 488
Capítulo 80: Metaprogramacion
Introducción
En C ++, la metaprogramación se refiere al uso de macros o plantillas para generar código en
tiempo de compilación.
En general, las macros están mal vistas en este rol y se prefieren las plantillas, aunque no son tan
genéricas.
Observaciones
La metaprogramación (o más específicamente, la metaprogramación de plantillas) es la práctica
de usar plantillas para crear constantes, funciones o estructuras de datos en tiempo de
compilación. Esto permite que los cálculos se realicen una vez en tiempo de compilación en lugar
de en cada tiempo de ejecución.
Examples
Cálculo de factoriales
#include <iostream>
template<>
struct factorial<0>
{
enum { value = 1 };
};
int main()
{
std::cout << factorial<7>::value << std::endl; // prints "5040"
}
https://riptutorial.com/es/home 489
factorial es una estructura, pero en la metaprogramación de plantillas se trata como una
metafunción de plantillas. Por convención, las metafunciones de la plantilla se evalúan verificando
un miembro en particular, ya sea ::type para las metafunciones que resultan en tipos, o ::value
para las metafunciones que generan valores.
Dado que las metafunciones de la plantilla se ejecutan en tiempo de compilación, sus resultados
se pueden usar en contextos que requieren valores de tiempo de compilación. Por ejemplo:
int my_array[factorial<5>::value];
Limitación : la mayoría de los compiladores no permitirán una profundidad de recursión más allá
de un límite. Por ejemplo, el compilador g++ por defecto limita la recursividad a 256 niveles. En el
caso de g++ , el programador puede establecer la profundidad de recursión usando la -ftemplate-
depth-X .
C ++ 11
Desde C ++ 11, la plantilla std::integral_constant se puede usar para este tipo de cálculo de
plantilla:
#include <iostream>
#include <type_traits>
template<>
struct factorial<0> :
std::integral_constant<long long, 1> {};
int main()
{
std::cout << factorial<7>::value << std::endl; // prints "5040"
}
https://riptutorial.com/es/home 490
#include <iostream>
int main()
{
char test[factorial(3)];
std::cout << factorial(7) << '\n';
}
C ++ 14
Desde C ++ 14, se han eliminado muchas restricciones para constexpr funciones constexpr y
ahora se pueden escribir mucho más convenientemente:
O incluso:
C ++ 17
#include <iostream>
#include <utility>
int main() {
https://riptutorial.com/es/home 491
std::cout << factorial<int, 5>::value << std::endl;
}
C ++ 11
print_all(os, rest...);
}
En su lugar, podríamos usar el truco de expansión, para realizar toda la transmisión en una sola
función. Esto tiene la ventaja de no necesitar una segunda sobrecarga, pero tiene la desventaja
de una legibilidad menor a la estelar:
C ++ 11
C ++ 17
Con C ++ 17, tenemos dos nuevas herramientas poderosas en nuestro arsenal para resolver este
problema. El primero es una expresión de plegado:
Y el segundo es if constexpr , que nos permite escribir nuestra solución recursiva original en una
sola función:
https://riptutorial.com/es/home 492
template <class T, class... Ts>
void print_all(std::ostream& os, T const& first, Ts const&... rest) {
os << first;
template<std::size_t N>
using make_index_sequence = make_integer_sequence<std::size_t, N>;
Si bien esto es estándar en C ++ 14, esto puede implementarse utilizando las herramientas de C
++ 11.
Podemos usar esta herramienta para llamar a una función con un std::tuple de argumentos
(estandarizados en C ++ 17 como std::apply ):
namespace detail {
template <class F, class Tuple, std::size_t... Is>
decltype(auto) apply_impl(F&& f, Tuple&& tpl, std::index_sequence<Is...> ) {
return std::forward<F>(f)(std::get<Is>(std::forward<Tuple>(tpl))...);
}
}
https://riptutorial.com/es/home 493
auto some_args = std::make_tuple(42, 'x', 3.14);
int r = apply(f, some_args); // calls f(42, 'x', 3.14)
Despacho de etiquetas
Una forma sencilla de seleccionar entre funciones en tiempo de compilación es enviar una función
a un par de funciones sobrecargadas que toman una etiqueta como un argumento (generalmente
el último). Por ejemplo, para implementar std::advance() , podemos enviar en la categoría de
iterador:
namespace details {
template <class RAIter, class Distance>
void advance(RAIter& it, Distance n, std::random_access_iterator_tag) {
it += n;
}
El envío de etiquetas puede darle un código que es mucho más fácil de leer que los equivalentes
https://riptutorial.com/es/home 494
utilizando SFINAE y enable_if .
Nota: mientras que C ++ 17, if constexpr puede simplificar la implementación del advance en
particular, no es adecuado para implementaciones abiertas a diferencia del envío de etiquetas.
Es posible detectar si se puede llamar a un operador o función en un tipo. Para probar si una
clase tiene una sobrecarga de std::hash , uno puede hacer esto:
template<class T>
struct has_hash<T, decltype(std::hash<T>()(std::declval<T>()), void())>
: std::true_type
{};
C ++ 17
Desde C ++ 17, std::void_t puede usarse para simplificar este tipo de construcción
template<class T>
struct has_hash<T, std::void_t< decltype(std::hash<T>()(std::declval<T>())) > >
: std::true_type
{};
Para detectar si un operador, como el operator< está definido, la sintaxis es casi la misma:
template<class T>
https://riptutorial.com/es/home 495
struct has_less_than<T, decltype(std::declval<T>() < std::declval<T>(), void())>
: std::true_type
{};
Se pueden usar para usar std::unordered_map<T> si T tiene una sobrecarga para std::hash , pero de
lo contrario intente usar std::map<T> :
Con C ++ 11 y los cálculos superiores en tiempo de compilación puede ser mucho más fácil. Por
ejemplo, el cálculo de la potencia de un número dado en el momento de la compilación será el
siguiente:
Nota: En C ++ 11, la función constexpr debe componerse solo desde una declaración de retorno.
Ventajas: Comparando esto con la forma estándar de cálculo del tiempo de compilación, este
método también es útil para los cálculos de tiempo de ejecución. Esto significa que si los
argumentos de la función no se conocen en el momento de la compilación (por ejemplo, el valor y
la potencia se dan como entrada a través del usuario), entonces la función se ejecuta en un
tiempo de compilación, por lo que no es necesario duplicar un código (ya que sería forzado en los
estándares más antiguos de C ++).
P.ej
void useExample() {
constexpr int compileTimeCalculated = calculatePower(3, 3); // computes at compile time,
// as both arguments are known at compilation time
// and used for a constant expression.
int value;
std::cin >> value;
int runtimeCalculated = calculatePower(value, 3); // runtime calculated,
// because value is known only at runtime.
}
C ++ 17
https://riptutorial.com/es/home 496
Otra forma de calcular la potencia en tiempo de compilación puede utilizar la expresión de
plegado de la siguiente manera:
#include <iostream>
#include <utility>
int main() {
std::cout << power<int, 4, 2>::value << std::endl;
}
Al implementar SFINAE utilizando std::enable_if , a menudo es útil tener acceso a las plantillas
de ayuda que determinan si un tipo T dado coincide con un conjunto de criterios.
Para ayudarnos con eso, el estándar ya proporciona dos tipos analógicos a true y false que son
std::true_type y std::false_type .
El siguiente ejemplo muestra cómo detectar si un tipo T es un puntero o no, la plantilla is_pointer
imita el comportamiento del std::is_pointer estándar std::is_pointer helper:
https://riptutorial.com/es/home 497
• Use ::value , por ejemplo, is_pointer<int>::value - value es un miembro de clase estática de
tipo bool heredado de std::true_type o std::false_type ;
• Construya un objeto de este tipo, por ejemplo, is_pointer<int>{} - Esto funciona porque
std::is_pointer hereda su constructor predeterminado de std::true_type o std::false_type
(que tienen constexpr constructores) y std::true_type y std::false_type tiene constexpr
operadores de conversión a bool .
C ++ 17
Si-entonces-de lo contrario
C ++ 11
template<typename T>
struct ValueOrPointer
{
typename std::conditional<(sizeof(T) > sizeof(void*)), T*, T>::type vop;
};
C ++ 11
Es posible escribir una función genérica (por ejemplo, min ) que acepte varios tipos numéricos y
un conteo de argumentos arbitrarios mediante la metaprogramación de la plantilla. Esta función
declara un min para dos argumentos y recursivamente para más.
https://riptutorial.com/es/home 498
return a < b ? a : b;
}
https://riptutorial.com/es/home 499
Capítulo 81: Metaprogramacion aritmica
Introducción
Estos son ejemplos del uso de la metaprogramación de plantillas C ++ en el procesamiento de
operaciones aritméticas en tiempo de compilación.
Examples
Cálculo de la potencia en O (log n)
Este ejemplo muestra una forma eficiente de calcular la potencia mediante la metaprogramación
de plantillas.
Ejemplo de uso:
C ++ 14
https://riptutorial.com/es/home 500
constexpr static double value = exponent < 0 ? (1.0 / intermediateValue) :
intermediateValue;
};
int main()
{
std::cout << powerDouble<2,-3>::value;
}
https://riptutorial.com/es/home 501
Capítulo 82: Modelo de memoria C ++ 11
Observaciones
Los diferentes subprocesos que intentan acceder a la misma ubicación de memoria participan en
una carrera de datos si al menos una de las operaciones es una modificación (también conocida
como operación de almacenamiento ). Estas razas de datos causan un comportamiento indefinido
. Para evitarlos, es necesario evitar que estos subprocesos ejecuten simultáneamente dichas
operaciones en conflicto.
Las primitivas de sincronización (exclusión mutua, sección crítica y similares) pueden proteger
tales accesos. El modelo de memoria introducido en C ++ 11 define dos nuevas formas portátiles
de sincronizar el acceso a la memoria en un entorno de múltiples subprocesos: operaciones
atómicas y cercas.
Operaciones atómicas
Ahora es posible leer y escribir en una ubicación de memoria determinada mediante el uso de
carga atómica y operaciones de almacenamiento atómico . Para mayor comodidad, estos se
incluyen en la clase de plantilla std::atomic<t> . Esta clase ajusta un valor de tipo t pero esta vez
se carga y se almacena en el objeto atómico.
La plantilla no está disponible para todos los tipos. Los tipos disponibles son específicos de la
implementación, pero esto generalmente incluye la mayoría (o todos) los tipos integrales
disponibles, así como los tipos de punteros. Así que std::atomic<unsigned> y
std::atomic<std::vector<foo> *> deberían estar disponibles, mientras que
std::atomic<std::pair<bool,char>> probablemente no estará disponible.
https://riptutorial.com/es/home 502
std :: memory_order Sentido
Consistencia secuencial
Si no se especifica ningún orden de memoria para una operación atómica, el orden se establece
de manera predeterminada en consistencia secuencial . Este modo también se puede seleccionar
explícitamente etiquetando la operación con std::memory_order_seq_cst .
Con este orden, ninguna operación de memoria puede cruzar la operación atómica. Todas las
operaciones de memoria secuenciadas antes de la operación atómica suceden antes de la
operación atómica y la operación atómica ocurre antes de todas las operaciones de memoria que
se secuencian después de esta. Este modo es probablemente el más fácil de razonar, pero
también conduce a la mayor penalización para el rendimiento. También evita todas las
optimizaciones del compilador que de otro modo podrían intentar reordenar las operaciones
después de la operación atómica.
Pedidos relajados
Liberar-Adquirir pedidos
https://riptutorial.com/es/home 503
Cuando la adquisición de carga ve el valor escrito por un lanzamiento de tienda, sucede lo
siguiente: todas las operaciones de almacenamiento secuenciadas antes de la liberación de
almacenamiento se vuelven visibles ( suceden antes ) las operaciones de carga que se
secuencian después de la adquisición de carga .
También tenga en cuenta que no hay liberación de carga atómica o adquisición de almacén
atómica . Intentar crear tales operaciones hace que sean operaciones relajadas .
Esta combinación es similar a la adquisición por versión , pero esta vez la carga atómica se
etiqueta con std::memory_order_consume y se convierte en una std::memory_order_consume (atómica)
de consumo de carga . Este modo es el mismo que el de adquisición de versión, con la única
diferencia de que entre las operaciones de carga secuenciadas después de la carga y el
consumo, solo en función del valor cargado por la carga y el consumo se ordenan.
Vallas
Las cercas también permiten que las operaciones de memoria se ordenen entre hilos. Una valla
es una valla de liberación o adquirir una valla.
Si una valla de liberación ocurre antes de una cerca de adquisición, entonces las tiendas
secuenciadas antes de la cerca de liberación son visibles para las cargas secuenciadas después
de la cerca de adquisición. Para garantizar que el cerco de liberación ocurra antes del cercado de
adquisición, se pueden usar otras primitivas de sincronización, incluidas las operaciones atómicas
relajadas.
Examples
Necesidad de modelo de memoria
int x, y;
bool ready = false;
void init()
{
x = 2;
y = 3;
https://riptutorial.com/es/home 504
ready = true;
}
void use()
{
if (ready)
std::cout << x + y;
}
Un subproceso llama a la función init() mientras que otro subproceso (o controlador de señales)
llama a la función use() . Uno podría esperar que la función use() imprima 5 o no haga nada. Esto
puede no ser siempre el caso por varias razones:
• La CPU puede reordenar las escrituras que ocurren en init() para que el código que
realmente se ejecuta pueda tener el siguiente aspecto:
void init()
{
ready = true;
x = 2;
y = 3;
}
• La CPU puede reordenar las lecturas que ocurren en use() para que el código realmente
ejecutado se convierta en:
void use()
{
int local_x = x;
int local_y = y;
if (ready)
std::cout << local_x + local_y;
}
int x, y;
std::atomic<bool> ready{false};
void init()
{
x = 2;
y = 3;
https://riptutorial.com/es/home 505
ready.store(true, std::memory_order_release);
}
void use()
{
if (ready.load(std::memory_order_acquire))
std::cout << x + y;
}
Aquí init() realiza una operación de liberación de tienda atómica . Esto no solo almacena el valor
true en ready , sino que también le dice al compilador que no puede mover esta operación antes
de las operaciones de escritura que se secuencian antes .
La función use() realiza una operación de adquisición de carga atómica . Lee el valor actual de
ready y también prohíbe al compilador colocar las operaciones de lectura que se secuencian
después de que suceda antes de la carga atómica .
Estas operaciones atómicas también hacen que el compilador ponga todas las instrucciones de
hardware necesarias para informar a la CPU que se abstenga de realizar reordenamientos no
deseados.
Tenga en cuenta que el compilador y la CPU todavía pueden escribir en y antes de escribir en x ,
y de manera similar, las lecturas de estas variables en use() pueden suceder en cualquier orden.
Ejemplo de valla
El ejemplo anterior también se puede implementar con cercas y operaciones atómicas relajadas:
int x, y;
std::atomic<bool> ready{false};
void init()
{
x = 2;
y = 3;
atomic_thread_fence(std::memory_order_release);
ready.store(true, std::memory_order_relaxed);
}
void use()
{
if (ready.load(std::memory_order_relaxed))
{
atomic_thread_fence(std::memory_order_acquire);
std::cout << x + y;
}
}
https://riptutorial.com/es/home 506
Si la operación de carga atómica ve el valor escrito por el almacén atómico, entonces el
almacenamiento ocurre antes de la carga, y también lo hacen las cercas: la cerca de liberación
ocurre antes de la cerca de adquisición, haciendo que las escrituras a x e y que preceden a la
cerca de liberación se hagan visibles. a la instrucción std::cout que sigue a la valla de
adquisición.
Una valla podría ser beneficiosa si puede reducir el número total de operaciones de adquisición,
liberación u otras operaciones de sincronización. Por ejemplo:
void block_and_use()
{
while (!ready.load(std::memory_order_relaxed))
;
atomic_thread_fence(std::memory_order_acquire);
std::cout << x + y;
}
La función block_and_use() gira hasta que la block_and_use() ready se establece con la ayuda de
carga atómica relajada. Luego, se utiliza una sola guía de adquisición para proporcionar el orden
de memoria necesario.
https://riptutorial.com/es/home 507
Capítulo 83: Mover la semantica
Examples
Mover la semantica
Mover la semántica es una forma de mover un objeto a otro en C ++. Para ello, vaciamos el
objeto antiguo y colocamos todo lo que tenía en el nuevo objeto.
Para esto, debemos entender qué es una referencia rvalue. Una referencia rvalue ( T&& donde T
es el tipo de objeto) no es muy diferente de una referencia normal ( T& , ahora se llama
referencias lvalue). Pero actúan como 2 tipos diferentes, y así, podemos hacer constructores o
funciones que toman un tipo u otro, lo que será necesario cuando se trata de la semántica de
movimientos.
La razón por la que necesitamos dos tipos diferentes es para especificar dos comportamientos
diferentes. Los constructores de referencia de valores están relacionados con la copia, mientras
que los constructores de referencia de valores están relacionados con el movimiento.
Para mover un objeto, usaremos std::move(obj) . Esta función devuelve una referencia de valor al
objeto, por lo que podemos robar los datos de ese objeto en uno nuevo. Hay varias maneras de
hacer esto que se discuten a continuación.
Es importante tener en cuenta que el uso de std::move crea solo una referencia rvalue. En otras
palabras, la declaración std::move(obj) no cambia el contenido de obj, mientras que auto obj2 =
std::move(obj) (posiblemente) sí lo hace.
Mover constructor
class A {
public:
int a;
int b;
A(const A &other) {
this->a = other.a;
this->b = other.b;
}
};
Para crear un constructor de copia, es decir, para hacer una función que copie un objeto y cree
uno nuevo, normalmente elegiríamos la sintaxis que se muestra arriba, tendríamos un constructor
para A que toma una referencia a otro objeto de tipo A, y copiaríamos el objeto manualmente
dentro del método.
Alternativamente, podríamos haber escrito A(const A &) = default; que copia automáticamente
https://riptutorial.com/es/home 508
sobre todos los miembros, haciendo uso de su copia constructor.
Para crear un constructor de movimientos, sin embargo, tomaremos una referencia rvalue en
lugar de una referencia lvalue, como aquí.
class Wallet {
public:
int nrOfDollars;
Wallet(Wallet &&other) {
this->nrOfDollars = other.nrOfDollars;
other.nrOfDollars = 0;
}
};
Tenga en cuenta que establecemos los valores antiguos en zero . El constructor de movimiento
predeterminado ( Wallet(Wallet&&) = default; ) copia el valor de nrOfDollars , ya que es un POD.
Como las semánticas de movimiento están diseñadas para permitir el estado de "robo" de la
instancia original, es importante considerar cómo debería verse la instancia original después de
este robo. En este caso, si no cambiáramos el valor a cero, habríamos duplicado la cantidad de
dólares en juego.
Wallet a;
a.nrOfDollars = 1;
Wallet b (std::move(a)); //calling B(B&& other);
std::cout << a.nrOfDollars << std::endl; //0
std::cout << b.nrOfDollars << std::endl; //1
Si bien lo anterior es un ejemplo simple, muestra lo que se pretende que haga el constructor de
movimientos. Se vuelve más útil en casos más complejos, como cuando se trata de la gestión de
recursos.
HeapHelper<T>* h_helper;
StackHelper<T> s_helper;
// ...
public:
// Default constructor & Rule of Five.
OperationsManager() : h_helper(new HeapHelper<T>) {}
OperationsManager(const MyType& other)
https://riptutorial.com/es/home 509
: h_helper(new HeapHelper<T>(*other.h_helper)), s_helper(other.s_helper) {}
MyType& operator=(MyType copy) {
swap(*this, copy);
return *this;
}
~OperationsManager() {
if (h_helper) { delete h_helper; }
}
// Copy/move helper.
friend void swap(MyType& left, MyType& right) noexcept {
std::swap(left.h_helper, right.h_helper);
std::swap(left.s_helper, right.s_helper);
}
};
Mover la tarea
De manera similar a cómo podemos asignar un valor a un objeto con una referencia de valor l, al
copiarlo, también podemos mover los valores de un objeto a otro sin construir uno nuevo.
Llamamos a esta asignación de movimiento. Movemos los valores de un objeto a otro objeto
existente.
Para esto, tendremos que sobrecargar el operator = , no para que tome una referencia de lvalor,
como en la asignación de copia, sino para que tome una referencia de rvalor.
class A {
int a;
A& operator= (A&& other) {
this->a = other.a;
other.a = 0;
return *this;
}
};
https://riptutorial.com/es/home 510
A a;
a.a = 1;
A b;
b = std::move(a); //calling A& operator= (A&& other)
std::cout << a.a << std::endl; //0
std::cout << b.a << std::endl; //1
• soporte especial para un operador de auto operator=(T&&) -> T& asignación operador de
movimiento auto operator=(T&&) -> T& , que también se supone que se mueve desde la
fuente.
En su artículo "Contenedores que nunca cambian" en el Dr. Dobbs Journal del 19 de septiembre
de 2013 , Andrew Koenig presentó un ejemplo interesante de ineficiencia algorítmica al usar un
estilo de programación donde las variables son inmutables después de la inicialización. Con este
estilo los bucles se expresan generalmente mediante recursión. Y para algunos algoritmos como
la generación de una secuencia de Collatz, la recursión requiere copiar lógicamente un
contenedor:
https://riptutorial.com/es/home 511
// Includes here, e.g. <vector>
namespace my {
template< class Item >
using Vector_ = /* E.g. std::vector<Item> */;
#include <iostream>
using namespace std;
auto main() -> int
{
for( int const x : my::collatz( 42 ) )
{
cout << x << ' ';
}
cout << '\n';
}
Salida:
42 21 64 32 16 8 4 2
https://riptutorial.com/es/home 512
En números concretos, con los compiladores g ++ y Visual C ++, la invocación anterior de
collatz(42) dio como resultado una secuencia de Collatz de 8 elementos y 36 operaciones de
copia de elementos (8 * collatz(42) = 28, más algunas) en llamadas de constructor de copia
vectorial.
using std::move;
Aquí, con los compiladores g ++ y Visual C ++, el número de operaciones de copia de elementos
debidas a invocaciones del constructor de copia vectorial fue exactamente 0.
https://riptutorial.com/es/home 513
Con algún soporte de lenguaje, quizás se pueda usar mover y aún expresar y aplicar la
inmutabilidad de una variable entre su inicialización y su movimiento final , después de lo cual
cualquier uso de esa variable debería ser un error. Por desgracia, a partir de C ++ 14 C ++ no es
compatible con eso. Para el código sin bucle, el movimiento sin uso después de la mudanza se
puede aplicar mediante una nueva declaración del nombre relevante como una struct incompleta,
como con el struct result; arriba, pero esto es feo y no es probable que sea entendido por otros
programadores; También los diagnósticos pueden ser bastante engañosos.
Para completar, la clase vectorial instrumentada utilizada para medir el número de operaciones de copia de
elementos debidas a invocaciones de constructor de copia:
vector<Item> items_;
public:
static auto n() -> int { return n_copy_ops(); }
Copy_tracking_vector(){}
https://riptutorial.com/es/home 514
std::cout << std::endl;
}
int main() {
// initialize vec1 with 1, 2, 3, 4 and vec2 as an empty vector
std::vector<int> vec1{1, 2, 3, 4};
std::vector<int> vec2;
int main() {
// initialize vec with 1, 2, 3, 4
std::vector<int> vec{1, 2, 3, 4};
https://riptutorial.com/es/home 515
Capítulo 84: Mutex recursivo
Examples
std :: recursive_mutex
La exclusión mutua recursiva permite que el mismo hilo bloquee recursivamente un recurso, hasta
un límite no especificado.
Hay muy pocas justificaciones de palabras reales para esto. Ciertas implementaciones complejas
pueden necesitar llamar a una copia sobrecargada de una función sin liberar el bloqueo.
std::atomic_int temp{0};
std::recursive_mutex _mutex;
std::this_thread::sleep_for(std::chrono::seconds(3));
std::unique_lock<std::recursive_mutex> lock( _mutex);
temp=0;
});
}
});
future1.get();
future2.get();
https://riptutorial.com/es/home 516
Capítulo 85: Mutexes
Observaciones
class shared_mutex
{
public:
typedef _Smtx_t * native_handle_type;
shared_mutex() _NOEXCEPT
: _Myhandle(0)
{ // default construct
}
~shared_mutex() _NOEXCEPT
{ // destroy the object
}
https://riptutorial.com/es/home 517
{ // lock non-exclusive
_Smtx_lock_shared(&_Myhandle);
}
Puede ver que std :: shared_mutex está implementado en Windows Slim Reader / Write Locks (
https://msdn.microsoft.com/ko-kr/library/windows/desktop/aa904937(v=vs.85).aspx)
https://riptutorial.com/es/home 518
El siguiente código es la implementación de MSVC14.1 de
std :: shared_timed_mutex.
class shared_timed_mutex
{
typedef unsigned int _Read_cnt_t;
static constexpr _Read_cnt_t _Max_readers = _Read_cnt_t(-1);
public:
shared_timed_mutex() _NOEXCEPT
: _Mymtx(), _Read_queue(), _Write_queue(),
_Readers(0), _Writing(false)
{ // default construct
}
~shared_timed_mutex() _NOEXCEPT
{ // destroy the object
}
void lock()
{ // lock exclusive
unique_lock<mutex> _Lock(_Mymtx);
while (_Writing)
_Write_queue.wait(_Lock);
_Writing = true;
while (0 < _Readers)
_Read_queue.wait(_Lock); // wait for writing, no readers
}
bool try_lock()
{ // try to lock exclusive
lock_guard<mutex> _Lock(_Mymtx);
if (_Writing || 0 < _Readers)
return (false);
else
{ // set writing, no readers
_Writing = true;
return (true);
}
}
template<class _Rep,
class _Period>
bool try_lock_for(
const chrono::duration<_Rep, _Period>& _Rel_time)
{ // try to lock for duration
return (try_lock_until(chrono::steady_clock::now() + _Rel_time));
}
template<class _Clock,
class _Duration>
bool try_lock_until(
const chrono::time_point<_Clock, _Duration>& _Abs_time)
{ // try to lock until time point
auto _Not_writing = [this] { return (!_Writing); };
auto _Zero_readers = [this] { return (_Readers == 0); };
unique_lock<mutex> _Lock(_Mymtx);
https://riptutorial.com/es/home 519
_Writing = true;
return (true);
}
void unlock()
{ // unlock exclusive
{ // unlock before notifying, for efficiency
lock_guard<mutex> _Lock(_Mymtx);
_Writing = false;
}
_Write_queue.notify_all();
}
void lock_shared()
{ // lock non-exclusive
unique_lock<mutex> _Lock(_Mymtx);
while (_Writing || _Readers == _Max_readers)
_Write_queue.wait(_Lock);
++_Readers;
}
bool try_lock_shared()
{ // try to lock non-exclusive
lock_guard<mutex> _Lock(_Mymtx);
if (_Writing || _Readers == _Max_readers)
return (false);
else
{ // count another reader
++_Readers;
return (true);
}
}
template<class _Rep,
class _Period>
bool try_lock_shared_for(
const chrono::duration<_Rep, _Period>& _Rel_time)
{ // try to lock non-exclusive for relative time
return (try_lock_shared_until(_Rel_time
+ chrono::steady_clock::now()));
}
template<class _Time>
bool _Try_lock_shared_until(_Time _Abs_time)
{ // try to lock non-exclusive until absolute time
auto _Can_acquire = [this] {
return (!_Writing && _Readers < _Max_readers); };
unique_lock<mutex> _Lock(_Mymtx);
https://riptutorial.com/es/home 520
if (!_Write_queue.wait_until(_Lock, _Abs_time, _Can_acquire))
return (false);
++_Readers;
return (true);
}
template<class _Clock,
class _Duration>
bool try_lock_shared_until(
const chrono::time_point<_Clock, _Duration>& _Abs_time)
{ // try to lock non-exclusive until absolute time
return (_Try_lock_shared_until(_Abs_time));
}
void unlock_shared()
{ // unlock non-exclusive
_Read_cnt_t _Local_readers;
bool _Local_writing;
~stl_condition_variable_win7() = delete;
stl_condition_variable_win7(const stl_condition_variable_win7&) = delete;
stl_condition_variable_win7& operator=(const stl_condition_variable_win7&) = delete;
https://riptutorial.com/es/home 521
virtual void wait(stl_critical_section_interface *lock) override
{
if (!stl_condition_variable_win7::wait_for(lock, INFINITE))
std::terminate();
}
private:
CONDITION_VARIABLE m_condition_variable;
};
void useSTLSharedMutex()
{
std::shared_mutex shared_mtx_lock;
https://riptutorial.com/es/home 522
std::vector<std::thread> readThreads;
std::vector<std::thread> writeThreads;
std::list<int> data = { 0 };
volatile bool exit = false;
std::atomic<int> readProcessedCnt(0);
std::atomic<int> writeProcessedCnt(0);
while (true)
{
shared_mtx_lock.lock_shared();
mydata.push_back(data.back());
++localProcessCnt;
shared_mtx_lock.unlock_shared();
if (exit)
break;
}
std::atomic_fetch_add(&readProcessedCnt, localProcessCnt);
}));
int localProcessCnt = 0;
while (true)
{
shared_mtx_lock.lock();
data.push_back(rand() % 100);
++localProcessCnt;
shared_mtx_lock.unlock();
if (exit)
break;
}
std::atomic_fetch_add(&writeProcessedCnt, localProcessCnt);
}));
}
std::this_thread::sleep_for(std::chrono::milliseconds(MAIN_WAIT_MILLISECONDS));
exit = true;
https://riptutorial.com/es/home 523
for (auto &r : readThreads)
r.join();
void useSTLSharedTimedMutex()
{
std::shared_timed_mutex shared_mtx_lock;
std::vector<std::thread> readThreads;
std::vector<std::thread> writeThreads;
std::list<int> data = { 0 };
volatile bool exit = false;
std::atomic<int> readProcessedCnt(0);
std::atomic<int> writeProcessedCnt(0);
while (true)
{
shared_mtx_lock.lock_shared();
mydata.push_back(data.back());
++localProcessCnt;
shared_mtx_lock.unlock_shared();
if (exit)
break;
}
std::atomic_fetch_add(&readProcessedCnt, localProcessCnt);
}));
int localProcessCnt = 0;
while (true)
{
shared_mtx_lock.lock();
data.push_back(rand() % 100);
++localProcessCnt;
https://riptutorial.com/es/home 524
shared_mtx_lock.unlock();
if (exit)
break;
}
std::atomic_fetch_add(&writeProcessedCnt, localProcessCnt);
}));
}
std::this_thread::sleep_for(std::chrono::milliseconds(MAIN_WAIT_MILLISECONDS));
exit = true;
Examples
std :: unique_lock, std :: shared_lock, std :: lock_guard
#include <unordered_map>
#include <mutex>
#include <shared_mutex>
#include <thread>
#include <string>
#include <iostream>
class PhoneBook {
public:
std::string getPhoneNo( const std::string & name )
{
std::shared_lock<std::shared_timed_mutex> l(_protect);
auto it = _phonebook.find( name );
if ( it != _phonebook.end() )
return (*it).second;
https://riptutorial.com/es/home 525
return "";
}
void addPhoneNo ( const std::string & name, const std::string & phone )
{
std::unique_lock<std::shared_timed_mutex> l(_protect);
_phonebook[name] = phone;
}
std::shared_timed_mutex _protect;
std::unordered_map<std::string,std::string> _phonebook;
};
Al crear un std :: unique_lock, hay tres estrategias de bloqueo diferentes para elegir:
std::try_to_lock , std::defer_lock y std::adopt_lock
{
std::atomic_int temp {0};
std::mutex _mutex;
std::thread t( [&](){
if(lock.owns_lock()){
//do something
temp=0;
}
}
});
while ( true )
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::unique_lock<std::mutex> lock( _mutex, std::try_to_lock);
if(lock.owns_lock()){
if (temp < INT_MAX){
++temp;
}
std::cout << temp << std::endl;
}
}
}
2. std::defer_lock permite crear una estructura de bloqueo sin adquirir el bloqueo. Cuando se
bloquea más de un mutex, hay una ventana de oportunidad para un interbloqueo si dos
llamadores de funciones intentan adquirir los bloqueos al mismo tiempo:
{
std::unique_lock<std::mutex> lock1(_mutex1, std::defer_lock);
https://riptutorial.com/es/home 526
std::unique_lock<std::mutex> lock2(_mutex2, std::defer_lock);
lock1.lock()
lock2.lock(); // deadlock here
std::cout << "Locked! << std::endl;
//...
}
Con el siguiente código, pase lo que pase en la función, los bloqueos se adquieren y liberan en el
orden apropiado:
{
std::unique_lock<std::mutex> lock1(_mutex1, std::defer_lock);
std::unique_lock<std::mutex> lock2(_mutex2, std::defer_lock);
std::lock(lock1,lock2); // no deadlock possible
std::cout << "Locked! << std::endl;
//...
3. std::adopt_lock no intenta bloquear una segunda vez si el subproceso que realiza la llamada
actualmente posee el bloqueo.
{
std::unique_lock<std::mutex> lock1(_mutex1, std::adopt_lock);
std::unique_lock<std::mutex> lock2(_mutex2, std::adopt_lock);
std::cout << "Locked! << std::endl;
//...
}
Algo a tener en cuenta es que std :: adopt_lock no es un sustituto para el uso de mutex recursivo.
Cuando el bloqueo se sale del ámbito de aplicación, se libera el mutex.
std :: mutex
std :: mutex es una estructura de sincronización simple y no recursiva que se utiliza para proteger
datos a los que se accede mediante varios subprocesos.
std::atomic_int temp{0};
std::mutex _mutex;
std::thread t( [&](){
temp=0;
}
});
while ( true )
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::unique_lock<std::mutex> lock( _mutex, std::try_to_lock);
https://riptutorial.com/es/home 527
if ( temp < INT_MAX )
temp++;
cout << temp << endl;
std::scoped_lockproporciona una semántica de estilo RAII para poseer una std::scoped_lock más,
combinada con los algoritmos de evitación de bloqueo utilizados por std::lock . Cuando se
destruye std::scoped_lock , los mutex se liberan en el orden inverso al que fueron adquiridos.
{
std::scoped_lock lock{_mutex1,_mutex2};
//do something
}
Tipos mutex
std :: bloqueo
std::lock utiliza algoritmos de evitación de puntos muertos para bloquear uno o más mutexes. Si
se lanza una excepción durante una llamada para bloquear varios objetos, std::lock desbloquea
los objetos bloqueados con éxito antes de volver a lanzar la excepción.
std::lock(_mutex1, _mutex2);
https://riptutorial.com/es/home 528
Capítulo 86: Objetos callables
Introducción
Los objetos recuperables son la colección de todas las estructuras de C ++ que se pueden usar
como una función. En la práctica, esto es todo lo que puede pasar a la función C ++ 17 STL
invoke () o que se puede usar en el constructor de la función std ::, que incluye: punteros de
función, clases con operador (), clases con implícito conversiones, referencias a funciones,
punteros a funciones miembros, punteros a datos de miembros, lambdas. Los objetos que se
pueden llamar se utilizan en muchos algoritmos STL como predicado.
Observaciones
Una charla muy útil de Stephan T. Lavavej ( <functional>: Novedades y uso apropiado ) (
Diapositivas ) nos lleva a la base de esta documentación.
Examples
Punteros a funciones
Los punteros de función son la forma más básica de pasar funciones, que también se pueden
utilizar en C. (Consulte la documentación de C para obtener más detalles).
A los efectos de los objetos que se pueden llamar, un puntero de función se puede definir como:
Si usaríamos un puntero de función para escribir nuestro propio ordenamiento vectorial, se vería
así:
https://riptutorial.com/es/home 529
struct GreaterThanInt {
static bool cmp(int lhs, int rhs) { return lhs > rhs; }
};
sortVectorInt(vectorOfInt, &GreaterThanInt::cmp); // Passes the pointer to a static member
function
Cada clase que sobrecargue al operator() puede usarse como un objeto de función. Estas clases
pueden ser escritas a mano (a menudo conocidas como funtores) o generadas automáticamente
por el compilador escribiendo Lambdas desde C ++ 11 en.
struct Person {
std::string name;
unsigned int age;
};
Como los funtores tienen su propia identidad, no se pueden colocar en un typedef y estos deben
aceptarse mediante el argumento de la plantilla. La definición de std::find_if puede verse como:
A partir de C ++ 17, la llamada del predicado se puede hacer con invocar: std::invoke(predicate,
*i) .
https://riptutorial.com/es/home 530
Capítulo 87: Operadores de Bits
Observaciones
Las operaciones de cambio de bits no son portátiles en todas las arquitecturas de procesadores,
diferentes procesadores pueden tener diferentes anchos de bits. En otras palabras, si escribiste
int a = ~0;
int b = a << 1;
Este valor sería diferente en una máquina de 64 bits en comparación con una máquina de 32 bits,
o de un procesador basado en x86 a un procesador basado en PIC.
No es necesario tener en cuenta la endiancia para las operaciones de bits en sí mismas, es decir,
el desplazamiento a la derecha ( >> ) desplazará los bits hacia el bit menos significativo y un XOR
realizará una exclusiva o en los bits. Endian-ness solo se debe tener en cuenta con los datos en
sí, es decir, si endian-ness es una preocupación para su aplicación, es una preocupación
independientemente de las operaciones de bits.
Examples
& - a nivel de bit y
std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
Salida
a = 6, b = 10, c = 2
Por qué
Un poco inteligente AND opera en el nivel de bits y utiliza la siguiente tabla de verdad booleana:
Cuando el valor binario para a ( 0110 ) y el valor binario para b ( 1010 ) son AND 'ed juntos,
obtenemos el valor binario de 0010 :
int a = 0 1 1 0
int b = 1 0 1 0 &
---------
int c = 0 0 1 0
https://riptutorial.com/es/home 531
El bit AND AND no cambia el valor de los valores originales a menos que esté específicamente
asignado para usar el operador compuesto de asignación bit a bit &= :
| - en modo bit o
std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
Salida
a = 5, b = 12, c = 13
Por qué
Un poco inteligente OR opera en el nivel de bits y utiliza la siguiente tabla de verdad booleana:
Cuando el valor binario para a ( 0101 ) y el valor binario para b ( 1100 ) se unen con OR , obtenemos
el valor binario de 1101 :
int a = 0 1 0 1
int b = 1 1 0 0 |
---------
int c = 1 1 0 1
El OR de bits no cambia el valor de los valores originales a menos que se asigne específicamente
para usar el operador compuesto de asignación de bits |= :
std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
Salida
a = 5, b = 9, c = 12
https://riptutorial.com/es/home 532
Por qué
Un poco inteligente XOR (exclusivo o) opera en el nivel de bits y utiliza la siguiente tabla de verdad
booleana:
Observe que con una operación XOR true OR true = false donde con las operaciones true AND/OR
true = true , de ahí la naturaleza exclusiva de la operación XOR.
Usando esto, cuando el valor binario para a ( 0101 ) y el valor binario para b ( 1001 ) son XOR 'ed
juntos obtenemos el valor binario de 1100 :
int a = 0 1 0 1
int b = 1 0 0 1 ^
---------
int c = 1 1 0 0
El XOR de bits no cambia el valor de los valores originales a menos que se asigne
específicamente para usar el operador compuesto de asignación de bits ^= :
Nota: el siguiente ejemplo se muestra a menudo como un ejemplo de un buen truco. Pero no se
debe utilizar en el código de producción (hay mejores formas en que std::swap() para lograr el
mismo resultado).
También puede utilizar una operación XOR para intercambiar dos variables sin un temporal:
int a = 42;
int b = 64;
// XOR swap
a ^= b;
b ^= a;
a ^= b;
std::cout << "a = " << a << ", b = " << b << "\n";
Para lograr esto, debe agregar un cheque para asegurarse de que pueda usarse.
https://riptutorial.com/es/home 533
{
// XOR swap
a ^= b;
b ^= a;
a ^= b;
}
}
Entonces, aunque parece un buen truco por sí solo, no es útil en código real. xor no es una
operación lógica básica, sino una combinación de otras: a ^ c = ~ (a & c) & (a | c)
también en el 2015+ las variables de los compiladores se pueden asignar como binarias:
int cn=0b0111;
Salida
a = 234, b = 21
Por qué
Un bit sabio NOT (complemento único) opera en el nivel de bits y simplemente voltea cada bit. Si
es un 1 , se cambia a un 0 , si es un 0 , se cambia a un 1 . El bit NO tiene el mismo efecto que
XOR'ing un valor contra el valor máximo para un tipo específico:
El bit bit NOT NO también puede ser una forma conveniente de verificar el valor máximo para un
tipo integral específico:
El bit NOT no cambia el valor del valor original y no tiene un operador de asignación compuesto,
por lo que no puede hacer a ~= 10 por ejemplo.
El bit NOT NOT ( ~ ) no debe confundirse con el NOT lógico ( ! ); donde un bit sabio NO volteará
cada bit, un lógico NO usará todo el valor para realizar su operación, en otras palabras (!1) !=
(~1)
https://riptutorial.com/es/home 534
<< - desplazamiento a la izquierda
int a = 1; // 0001b
int b = a << 1; // 0010b
std::cout << "a = " << a << ", b = " << b << std::endl;
Salida
a = 1, b = 2
Por qué
El desplazamiento en el bit a la izquierda desplazará los bits del valor de la mano izquierda ( a ) el
número especificado a la derecha ( 1 ), esencialmente rellenando los bits menos significativos con
0, de modo que se desplaza el valor de 5 (binario 0000 0101 ) hacia la izquierda 4 veces (por
ejemplo, 5 << 4 ) producirá el valor de 80 (binario 0101 0000 ). Es posible que tenga en cuenta que
desplazar un valor a la izquierda 1 vez también equivale a multiplicar el valor por 2, por ejemplo:
int a = 7;
while (a < 200) {
std::cout << "a = " << a << std::endl;
a <<= 1;
}
a = 7;
while (a < 200) {
std::cout << "a = " << a << std::endl;
a *= 2;
}
Pero debe tenerse en cuenta que la operación de desplazamiento a la izquierda desplazará todos
los bits a la izquierda, incluido el bit de signo, por ejemplo:
int a = 2147483647; // 0111 1111 1111 1111 1111 1111 1111 1111
int b = a << 1; // 1111 1111 1111 1111 1111 1111 1111 1110
std::cout << "a = " << a << ", b = " << b << std::endl;
Si bien algunos compiladores producirán resultados que parecen esperados, se debe tener en
cuenta que si deja un número con signo para cambiar de manera que el bit de signo se vea
afectado, el resultado no está definido . Tampoco está definido si el número de bits que desea
desplazar es un número negativo o es mayor que el número de bits que puede contener el tipo de
la izquierda, por ejemplo:
int a = 1;
int b = a << -1; // undefined behavior
char c = a << 20; // undefined behavior
El desplazamiento a la izquierda de bits no cambia el valor de los valores originales a menos que
https://riptutorial.com/es/home 535
se asigne específicamente para usar el operador compuesto de asignación de bits <<= :
int a = 5; // 0101b
a <<= 1; // a = a << 1;
int a = 2; // 0010b
int b = a >> 1; // 0001b
std::cout << "a = " << a << ", b = " << b << std::endl;
Salida
a = 2, b = 1
Por qué
El cambio de bit a la derecha desplazará los bits del valor de la mano izquierda ( a ) el número
especificado a la derecha ( 1 ); debe tenerse en cuenta que, si bien la operación de un cambio a
la derecha es estándar, lo que sucede con los bits de un cambio a la derecha en un número
negativo con signo está definido por la implementación y, por lo tanto, no se puede garantizar que
sea portátil, por ejemplo:
int a = -2;
int b = a >> 1; // the value of b will be depend on the compiler
Tampoco está definido si el número de bits que desea desplazar es un número negativo, por
ejemplo:
int a = 1;
int b = a >> -1; // undefined behavior
El desplazamiento a la derecha de los bits no cambia el valor de los valores originales a menos
que se asigne específicamente el uso del operador compuesto de asignación de bits >>= :
int a = 2; // 0010b
a >>= 1; // a = a >> 1;
https://riptutorial.com/es/home 536
Capítulo 88: Optimización en C ++
Examples
Optimización de clase base vacía
Un objeto no puede ocupar menos de 1 byte, ya que entonces los miembros de una matriz de
este tipo tendrían la misma dirección. Por lo tanto, sizeof(T)>=1 siempre se cumple. También es
cierto que una clase derivada no puede ser más pequeña que cualquiera de sus clases base. Sin
embargo, cuando la clase base está vacía, su tamaño no necesariamente se agrega a la clase
derivada:
En este caso, no es necesario asignar un byte para Base dentro de Derived para tener una
dirección distinta por tipo por objeto. Si se realiza la optimización de la clase base vacía (y no se
requiere relleno), entonces sizeof(Derived) == sizeof(int) , es decir, no se realiza ninguna
asignación adicional para la base vacía. Esto también es posible con varias clases base (en C ++,
las bases múltiples no pueden tener el mismo tipo, por lo que no surgen problemas).
Tenga en cuenta que esto solo se puede realizar si el primer miembro de Derived difiere en tipo de
cualquiera de las clases base. Esto incluye cualquier base común directa o indirecta. Si es el
mismo tipo que una de las bases (o hay una base común), se requiere al menos la asignación de
un solo byte para garantizar que no haya dos objetos distintos del mismo tipo que tengan la
misma dirección.
Introducción al rendimiento
C y C ++ son conocidos como lenguajes de alto rendimiento, en gran parte debido a la gran
cantidad de personalización del código, lo que permite a un usuario especificar el rendimiento
mediante la elección de la estructura.
https://riptutorial.com/es/home 537
no valer la pena para el otro 99%
• Microoptimización: los compiladores hacen esto muy eficientemente y la
microoptimización puede incluso afectar la capacidad de los compiladores para optimizar
aún más el código.
El enfoque más sencillo para optimizar es ejecutando menos código. Este enfoque generalmente
proporciona una aceleración fija sin cambiar la complejidad de tiempo del código.
A pesar de que este enfoque le proporciona una clara aceleración, esto solo proporcionará
mejoras notables cuando el código se llame mucho.
C ++ 14
Desde C ++ 14, los compiladores pueden optimizar este código para eliminar la asignación y la
desasignación correspondiente.
https://riptutorial.com/es/home 538
lookup.emplace_back(key, std::make_unique<A>());
return lookup[key].get();
}
// Within this function, we will have the same noticeable effect as the slow variant while
going at double speed as we only traverse once through the code
const A *lazyLookupSlow(const std::string &key) {
auto &value = lookup[key];
if (!value)
value = std::make_unique<A>();
return value.get();
}
Se puede utilizar un enfoque similar a esta optimización para implementar una versión estable de
dispositivos unique
https://riptutorial.com/es/home 539
Usando contenedores eficientes
Al usar un contenedor que usa una implementación diferente para almacenar sus elementos
(hash container en lugar de tree), podemos transformar nuestra implementación en la complejidad
N. Como efecto secundario, llamaremos al operador de comparación para std :: string less, ya
que solo debe llamarse cuando la cadena insertada debe terminar en el mismo cubo.
Al agregar una sobrecarga de memoria adicional y cálculos adicionales, intenta evitar una
asignación de pila costosa. Los beneficios de esta técnica dependen del uso y pueden incluso
afectar el rendimiento si se usan incorrectamente.
Ejemplo
https://riptutorial.com/es/home 540
Una forma muy ingenua de implementar una cadena con esta optimización sería lo siguiente:
#include <cstring>
public:
~string()
{
if (_isAllocated)
delete [] _buffer;
}
string(string &&rhs)
: _isAllocated(rhs._isAllocated)
, _buffer(rhs._buffer)
, _smallBuffer(rhs._smallBuffer) //< Not needed if allocated
{
if (_isAllocated)
{
// Prevent double deletion of the memory
rhs._buffer = nullptr;
}
else
{
// Copy over data
std::strcpy(_smallBuffer, rhs._smallBuffer);
_buffer = &_smallBuffer[0];
}
}
// Other methods, including other constructors, copy constructor,
// assignment operators have been omitted for readability
};
Como puede ver en el código anterior, se ha agregado cierta complejidad adicional para evitar
algunas operaciones new y de delete . Además de esto, la clase tiene una huella de memoria
mayor que no se puede usar, excepto en un par de casos.
A menudo se intenta codificar el valor bool _isAllocated , dentro del puntero _buffer con la
manipulación de bits para reducir el tamaño de una instancia (Intel 64 bit: podría reducir el tamaño
https://riptutorial.com/es/home 541
en 8 bytes). Una optimización que solo es posible cuando se conoce cuáles son las reglas de
alineación de la plataforma.
¿Cuándo usar?
Como esta optimización agrega mucha complejidad, no se recomienda usar esta optimización en
todas las clases. A menudo se encontrará en estructuras de datos de bajo nivel de uso común. En
las implementaciones comunes de standard library C ++ 11 se pueden encontrar usos en
std::basic_string<> y std::function<> .
Como esta optimización solo evita las asignaciones de memoria cuando los datos almacenados
son más pequeños que el búfer, solo dará beneficios si la clase se usa a menudo con datos
pequeños.
https://riptutorial.com/es/home 542
Capítulo 89: Palabra clave amigo
Introducción
Las clases bien diseñadas encapsulan su funcionalidad, ocultan su implementación y
proporcionan una interfaz limpia y documentada. Esto permite un nuevo diseño o cambio siempre
que la interfaz no se modifique.
En un escenario más complejo, pueden requerirse múltiples clases que dependen de los detalles
de implementación de cada uno. Las clases y funciones de Friend permiten a estos compañeros
acceder a los detalles de los demás, sin comprometer la encapsulación y el ocultamiento de la
información de la interfaz documentada.
Examples
Función de amigo
Una clase o una estructura puede declarar cualquier función que sea amiga. Si una función es un
amigo de una clase, puede acceder a todos sus miembros protegidos y privados:
class PrivateHolder {
public:
PrivateHolder(int val) : private_value(val) {}
private:
int private_value;
// Declare one of the function as a friend.
friend void friend_function();
};
void non_friend_function() {
PrivateHolder ph(10);
// Compilation error: private_value is private.
std::cout << ph.private_value << std::endl;
}
void friend_function() {
// OK: friends may access private values.
PrivateHolder ph(10);
std::cout << ph.private_value << std::endl;
}
Los modificadores de acceso no alteran la semántica de los amigos. Las declaraciones públicas,
protegidas y privadas de un amigo son equivalentes.
https://riptutorial.com/es/home 543
class PrivateHolderDerived : public PrivateHolder {
public:
PrivateHolderDerived(int val) : PrivateHolder(val) {}
private:
int derived_private_value = 0;
};
void friend_function() {
PrivateHolderDerived pd(20);
// OK.
std::cout << pd.private_value << std::endl;
// Compilation error: derived_private_value is private.
std::cout << pd.derived_private_value << std::endl;
}
Método de amigo
class Accesser {
public:
void private_accesser();
};
class PrivateHolder {
public:
PrivateHolder(int val) : private_value(val) {}
friend void Accesser::private_accesser();
private:
int private_value;
};
void Accesser::private_accesser() {
PrivateHolder ph(10);
// OK: this method is declares as friend.
std::cout << ph.private_value << std::endl;
}
Clase de amigo
Una clase entera puede ser declarada como amiga. La declaración de clase de amigo significa
que cualquier miembro del amigo puede acceder a miembros privados y protegidos de la clase
declarante:
class Accesser {
public:
void private_accesser1();
void private_accesser2();
};
https://riptutorial.com/es/home 544
class PrivateHolder {
public:
PrivateHolder(int val) : private_value(val) {}
friend class Accesser;
private:
int private_value;
};
void Accesser::private_accesser1() {
PrivateHolder ph(10);
// OK.
std::cout << ph.private_value << std::endl;
}
void Accesser::private_accesser2() {
PrivateHolder ph(10);
// OK.
std::cout << ph.private_value + 1 << std::endl;
}
class Accesser {
public:
void private_accesser1();
void private_accesser2();
private:
int private_value = 0;
};
class PrivateHolder {
public:
PrivateHolder(int val) : private_value(val) {}
// Accesser is a friend of PrivateHolder
friend class Accesser;
void reverse_accesse() {
// but PrivateHolder cannot access Accesser's members.
Accesser a;
std::cout << a.private_value;
}
private:
int private_value;
};
https://riptutorial.com/es/home 545
Capítulo 90: palabra clave const
Sintaxis
• const Tipo myVariable = initial; // Declara una variable const; no puede ser cambiado
• Tipo const y myReference = myVariable; // Declara una referencia a una variable const.
• Tipo const * myPointer = & myVariable; // Declara un puntero a const. El puntero puede
cambiar, pero el miembro de datos subyacente no se puede cambiar a través del puntero
• Escriba * const myPointer = & myVariable; // Declara un puntero const. El puntero no se
puede reasignar para apuntar a otra cosa, pero el miembro de datos subyacente se puede
cambiar
• Tipo const * const myPointer = & myVariable; // Declara una constante puntero a const.
Observaciones
Una variable marcada como const no puede ser cambiada 1. Intentar llamar a cualquier operación
que no sea constante en él resultará en un error del compilador.
1: Bueno, se puede cambiar a través de const_cast , pero casi nunca deberías usar eso
Examples
Variables locales const
Declaración y uso.
int f = 0;
e = &f; // OK; e is a non-const pointer-to-const,
// which means that it can be rebound to new int* or const int*
int *g = &f;
*g = 1; // OK; this value still can be changed through dereferencing
// a pointer-not-to-const
https://riptutorial.com/es/home 546
Punteros const
int a = 0, b = 2;
pA = &b;
*pB = b;
Las funciones de miembro de una clase se pueden declarar const , que le dice al compilador y a
los futuros lectores que esta función no modificará el objeto:
class MyClass
{
private:
int myInt_;
public:
int myInt() const { return myInt_; }
void setMyInt(int myInt) { myInt_ = myInt; }
};
En una función miembro de const , this puntero es efectivamente una const MyClass * lugar de
una MyClass * . Esto significa que no puede cambiar ninguna variable miembro dentro de la
función; El compilador emitirá una advertencia. Entonces setMyInt no pudo ser declarado const .
Casi siempre debe marcar las funciones miembro como const cuando sea posible. Sólo se
pueden llamar a las funciones miembro const en una const MyClass .
static métodos static no pueden ser declarados como const . Esto se debe a que un método
estático pertenece a una clase y no se llama en un objeto; por lo tanto, nunca puede modificar las
variables internas del objeto. Así que declarar métodos static como const sería redundante.
En C ++, los métodos que difieren solo por el calificador const pueden sobrecargarse. A veces es
posible que se necesiten dos versiones de getter que devuelvan una referencia a algún miembro.
https://riptutorial.com/es/home 547
Deje que Foo sea una clase, que tiene dos métodos que realizan operaciones idénticas y devuelve
una referencia a un objeto de tipo Bar :
class Foo
{
public:
Bar& GetBar(/* some arguments */)
{
/* some calculations */
return bar;
}
// ...
};
La única diferencia entre ellos es que un método es non-const y devuelve una referencia non-
const (que se puede usar para modificar un objeto) y el segundo es const y devuelve una
referencia const.
Para evitar la duplicación de código, existe la tentación de llamar a un método desde otro. Sin
embargo, no podemos llamar al método non-const del const. Pero podemos llamar al método
const desde el no const. Eso requerirá usar "const_cast" para eliminar el calificador const.
La solucion es:
struct Foo
{
Bar& GetBar(/*arguments*/)
{
return const_cast<Bar&>(const_cast<const Foo*>(this)->GetBar(/*arguments*/));
}
En el código anterior, llamamos a la versión const de GetBar desde GetBar no const al GetBar esto
en tipo const: const_cast<const Foo*>(this) . Como llamamos al método const desde non-const, el
objeto en sí mismo es non-const y se permite desechar la const.
#include <iostream>
class Student
{
https://riptutorial.com/es/home 548
public:
char& GetScore(bool midterm)
{
return const_cast<char&>(const_cast<const Student*>(this)->GetScore(midterm));
}
private:
char midtermScore;
char finalScore;
};
int main()
{
// non-const object
Student a;
// We can assign to the reference. Non-const version of GetScore is called
a.GetScore(true) = 'B';
a.GetScore(false) = 'A';
// const object
const Student b(a);
// We still can call GetScore method of const object,
// because we have overloaded const version of GetScore
std::cout << b.GetScore(true) << b.GetScore(false) << '\n';
}
https://riptutorial.com/es/home 549
Capítulo 91: palabra clave mutable
Examples
modificador de miembro de clase no estático
mutable modificador mutable en este contexto se usa para indicar que un campo de datos de un
objeto const puede modificarse sin afectar el estado visible externamente del objeto.
Si tiene un campo de datos de bloqueo (por ejemplo, std::unique_lock ) que está bloqueado y
desbloqueado dentro de un método const, esta palabra clave también es lo que podría usar.
No debe usar esta palabra clave para romper la constancia lógica de un objeto.
class pi_calculator {
public:
double get_pi() const {
if (pi_calculated) {
return pi;
} else {
double new_pi = 0;
for (int i = 0; i < 1000000000; ++i) {
// some calculation to refine new_pi
}
// note: if pi and pi_calculated were not mutable, we would get an error from a
compiler
// because in a const method we can not change a non-mutable field
pi = new_pi;
pi_calculated = true;
return pi;
}
}
private:
mutable bool pi_calculated = false;
mutable double pi = 0;
};
lambdas mutables
Por defecto, el operator() implícito operator() de un lambda es const . Esto no permite realizar
operaciones no const en la lambda. Para permitir la modificación de miembros, un lambda puede
marcarse como mutable , lo que hace que el operator() implícito operator() no sea const :
int a = 0;
https://riptutorial.com/es/home 550
return a++; // error: operator() is const
// cannot modify members
};
good_counter(); // 0
good_counter(); // 1
good_counter(); // 2
https://riptutorial.com/es/home 551
Capítulo 92: Palabras clave
Introducción
Las palabras clave tienen un significado fijo definido por el estándar C ++ y no se pueden utilizar
como identificadores. Es ilegal redefinir palabras clave utilizando el preprocesador en cualquier
unidad de traducción que incluya un encabezado de biblioteca estándar. Sin embargo, las
palabras clave pierden su significado especial dentro de los atributos.
Sintaxis
• asm ( cadena-literal );
• noexcept ( expresión ) // significado 1
• noexcept ( expresión constante ) // significado 2
• noexcept // significado 2
• tamaño de la expresión unaria
• sizeof ( type-id )
• sizeof ... ( identificador ) // desde C ++ 11
• nombre de archivo identificador de nombre anidado identificador // que significa 1
• nombre de archivo plantilla de especificador de nombre anidado ( opt ) simple-template-id //
que significa 1
• identificador de nombre de tipo ( opt ) // significado 2
• typename ... identifier ( opt ) // significado 2; desde C ++ 11
• identificador de nombre de tipo ( opt ) = ID de tipo // significado 2
• plantilla < plantilla-lista-parámetro > nombre tipográfico ... ( opt ) identificador ( opt ) //
significado 3
• plantilla < plantilla-lista-parámetro > identificador de nombre de tipo ( opt ) = id-expresión //
significado 3
Observaciones
La lista completa de palabras clave es la siguiente:
https://riptutorial.com/es/home 552
• const
• constexpr (desde C ++ 11)
• const_cast
• continue
• decltype (desde C ++ 11)
• default
• delete para administración de memoria , para funciones (desde C ++ 11)
• do
• double
• dynamic_cast
• else
• enum
• explicit
• export
• extern como especificador de declaración , en especificación de vinculación , para plantillas
• false
• float
• for
• friend
• goto
• if
• inline para funciones , para espacios de nombres (desde C ++ 11), para variables (desde C
++ 17)
• int
• long
• mutable
• namespace
• new
• noexcept (desde C ++ 11)
• nullptr (desde C ++ 11)
• operator
• private
• protected
• public
• register
• reinterpret_cast
• return
• short
• signed
• sizeof
• static
• static_assert (desde C ++ 11)
• static_cast
• struct
• switch
• template
• this
• thread_local (desde C ++ 11)
• throw
• true
• try
• typedef
• typeid
• typename
https://riptutorial.com/es/home 553
• union
• unsigned
• using para redeclar un nombre , para un alias un espacio de nombres , para un alias un tipo
• virtual para funciones , para clases base.
• void
• volatile
• wchar_t
• while
Los tokens final y override no son palabras clave. Pueden usarse como identificadores y tienen
un significado especial solo en ciertos contextos.
Los tokens and , and_eq , bitand , bitor , compl , not , not_eq , or , or_eq , xor y xor_eq son ortografías
alternativas de && , &= , & , | , ~ ! , != , || , |= , ^ , y ^= , respectivamente. La norma no los trata
como palabras clave, pero son palabras clave para todos los propósitos y propósitos, ya que es
imposible redefinirlas o usarlas para significar otra cosa que no sean los operadores que
representan.
Los siguientes temas contienen explicaciones detalladas de muchas de las palabras clave en C
++, que sirven para propósitos fundamentales como nombrar tipos básicos o controlar el flujo de
ejecución.
• Control de flujo
• Iteración
• Clases / Estructuras
Examples
asm
La palabra clave asm toma un solo operando, que debe ser una cadena literal. Tiene un significado
definido por la implementación, pero generalmente se pasa al ensamblador de la implementación,
con la salida del ensamblador incorporada en la unidad de traducción.
La declaración asm es una definición , no una expresión , por lo que puede aparecer en el ámbito
del bloque o en el ámbito del espacio de nombres (incluido el ámbito global). Sin embargo, dado
que el ensamblaje en línea no puede estar limitado por las reglas del lenguaje C ++, asm puede no
aparecer dentro de una función constexpr .
https://riptutorial.com/es/home 554
Ejemplo:
explícito
1. Cuando se aplica a un constructor de un solo argumento, evita que ese constructor se use
para realizar conversiones implícitas.
class MyVector {
public:
explicit MyVector(uint64_t size);
};
MyVector v1(100); // ok
uint64_t len1 = 100;
MyVector v2{len1}; // ok, len1 is uint64_t
int len2 = 100;
MyVector v3{len2}; // ill-formed, implicit conversion from int to uint64_t
struct S {
explicit S(int x, int y);
};
S f() {
return {12, 34}; // ill-formed
return S{12, 34}; // ok
}
C ++ 11
2. Cuando se aplica a una función de conversión, evita que esa función de conversión se
utilice para realizar conversiones implícitas.
class C {
const int x;
public:
C(int x) : x(x) {}
explicit operator int() { return x; }
};
C c(42);
int x = c; // ill-formed
int y = static_cast<int>(c); // ok; explicit conversion
noexcept
C ++ 11
https://riptutorial.com/es/home 555
1. Un operador unario que determina si la evaluación de su operando puede propagar una
excepción. Tenga en cuenta que los cuerpos de las funciones llamadas no se examinan, por
lo que noexcept puede producir falsos negativos. El operando no es evaluado.
#include <iostream>
#include <stdexcept>
void foo() { throw std::runtime_error("oops"); }
void bar() {}
struct S {};
int main() {
std::cout << noexcept(foo()) << '\n'; // prints 0
std::cout << noexcept(bar()) << '\n'; // prints 0
std::cout << noexcept(1 + 1) << '\n'; // prints 1
std::cout << noexcept(S()) << '\n'; // prints 1
}
En este ejemplo, aunque bar() nunca puede lanzar una excepción, noexcept(bar()) sigue
siendo falso porque el hecho de que bar() no puede propagar una excepción no se ha
especificado explícitamente.
2. Al declarar una función, especifica si la función puede o no propagar una excepción. Solo,
declara que la función no puede propagar una excepción. Con un argumento entre
paréntesis, declara que la función puede o no puede propagar una excepción dependiendo
del valor de verdad del argumento.
C ++ 17
Si una función es o no una noexcept es parte del tipo de función: es decir, en el ejemplo anterior,
f1 , f2 y f3 tienen diferentes tipos de f4 , f5 y f6 . Por lo tanto, noexcept también es significativo en
punteros de función, argumentos de plantilla, etc.
void g1() {}
void g2() noexcept {}
void (*p1)() noexcept = &g1; // ill-formed, since g1 is not noexcept
https://riptutorial.com/es/home 556
void (*p2)() noexcept = &g2; // ok; types match
void (*p3)() = &g1; // ok; types match
void (*p4)() = &g2; // ok; implicit conversion
escribe un nombre
1. Cuando le sigue un nombre calificado, typename especifica que es el nombre de un tipo. Esto
suele ser necesario en las plantillas, en particular, cuando el especificador de nombre
anidado es un tipo dependiente distinto de la instanciación actual. En este ejemplo,
std::decay<T> depende del parámetro de plantilla T , por lo tanto, para nombrar el tipo de type
anidado, debemos prefijar todo el nombre calificado con typename . Para más detalles, vea
¿Dónde y por qué tengo que colocar las palabras clave "plantilla" y "nombre de tipo"?
C ++ 17
tamaño de
Un operador unario que produce el tamaño en bytes de su operando, que puede ser una
expresión o un tipo. Si el operando es una expresión, no se evalúa. El tamaño es una expresión
constante de tipo std::size_t .
https://riptutorial.com/es/home 557
para obtener más detalles.
• Los tipos char , signed char y unsigned char tienen un tamaño de 1. A la inversa, un byte se
define como la cantidad de memoria necesaria para almacenar un objeto char . No significa
necesariamente 8 bits, ya que algunos sistemas tienen objetos de char más de 8 bits.
Si expr es una expresión, sizeof( expr ) es equivalente a sizeof(T) donde T es el tipo de expr.
int a[100];
std::cout << "The number of bytes in `a` is: " << sizeof a;
memset(a, 0, sizeof a); // zeroes out the array
C ++ 11
vacío C ++
1. Cuando se utiliza como un tipo de retorno de función, la palabra clave void especifica que la
función no devuelve un valor. Cuando se usa para la lista de parámetros de una función,
void especifica que la función no toma parámetros. Cuando se usa en la declaración de un
puntero, void especifica que el puntero es "universal".
2. Si el tipo de un puntero es nulo *, el puntero puede apuntar a cualquier variable que no esté
declarada con la palabra clave constante o volátil. Un puntero de vacío no se puede anular a
menos que se convierta a otro tipo. Un puntero vacío se puede convertir en cualquier otro
tipo de puntero de datos.
3. Un puntero de vacío puede apuntar a una función, pero no a un miembro de clase en C ++.
C ++ volátil
1. Un calificador de tipo que puede usar para declarar que un objeto puede ser modificado en
el programa por el hardware.
volatile declarator ;
https://riptutorial.com/es/home 558
C ++ virtual
1. La palabra clave virtual declara una función virtual o una clase base virtual.
Parámetros
este puntero
this->member-identifier
Un puntero de este objeto no es parte del objeto en sí; no se refleja en el resultado de una
sentencia sizeof en el objeto. En cambio, cuando se llama a una función miembro no estática para
un objeto, el compilador pasa la dirección del objeto como un argumento oculto a la función. Por
ejemplo, la siguiente llamada de función:
myDate.setMonth( 3 );
setMonth( &myDate, 3 );
The object's address is available from within the member function as the this pointer. Most
uses of this are implicit. It is legal, though unnecessary, to explicitly use this when
referring to members of the class. For example:
The expression *this is commonly used to return the current object from a member function:
return *this;
https://riptutorial.com/es/home 559
The this pointer is also used to guard against self-reference:
if (&Object != this) {
// do not execute in cases of self-reference
1. Para implementar el manejo de excepciones en C ++, use las expresiones try, throw y catch.
2. Primero, use un bloque de prueba para encerrar una o más declaraciones que puedan
generar una excepción.
3. Una expresión de lanzamiento indica que se ha producido una condición excepcional (a
menudo, un error) en un bloque try. Puede usar un objeto de cualquier tipo como el
operando de una expresión de lanzamiento. Normalmente, este objeto se utiliza para
comunicar información sobre el error. En la mayoría de los casos, recomendamos que use
la clase std :: exception o una de las clases derivadas que se definen en la biblioteca
estándar. Si uno de ellos no es apropiado, le recomendamos que obtenga su propia clase
de excepción de std :: exception.
4. Para manejar las excepciones que pueden ser lanzadas, implemente uno o más bloques
catch inmediatamente después de un bloque try. Cada bloque catch especifica el tipo de
excepción que puede manejar.
MyData md;
try {
// Code that could throw an exception
md = GetNetworkResource();
}
catch (const networkIOException& e) {
// Code that executes when an exception of type
// networkIOException is thrown in the try block
// ...
// Log error message in the exception object
cerr << e.what();
}
catch (const myDataFormatException& e) {
// Code that handles another exception type
// ...
cerr << e.what();
}
https://riptutorial.com/es/home 560
después de la cláusula catch es el controlador de excepciones. Este es el controlador
que captura la excepción que se produce si los tipos en las expresiones de
lanzamiento y captura son compatibles.
try {
throw CSomeOtherException();
}
catch(...) {
// Catch all exceptions – dangerous!!!
// Respond (perhaps only partially) to the exception, then
// re-throw to pass the exception to some other handler
// ...
throw;
}
amigo (C ++)
2. Si declara una función de amigo que no se había declarado previamente, esa función se
exporta al ámbito no clasificador adjunto.
class friend F
friend F;
class ForwardDeclared;// Class name is known.
class HasFriends
{
friend int ForwardDeclared::IsAFriend();// C2039 error expected
};
funciones de amigo
1. Una función amiga es una función que no es miembro de una clase pero tiene acceso a los
miembros privados y protegidos de la clase. Las funciones del amigo no se consideran
miembros de la clase; Son funciones externas normales que tienen privilegios de acceso
especiales.
2. Los amigos no están dentro del alcance de la clase, y no se les llama mediante los
operadores de selección de miembros (. Y ->) a menos que sean miembros de otra clase.
3. La clase que otorga acceso declara una función de amigo. La declaración de amigo se
puede colocar en cualquier parte de la declaración de clase. No se ve afectado por las
palabras clave de control de acceso.
#include <iostream>
https://riptutorial.com/es/home 561
using namespace std;
class Point
{
friend void ChangePrivate( Point & );
public:
Point( void ) : m_i(0) {}
void PrintPrivate( void ){cout << m_i << endl; }
private:
int m_i;
};
int main()
{
Point sPoint;
sPoint.PrintPrivate();
ChangePrivate(sPoint);
sPoint.PrintPrivate();
// Output: 0
1
}
class B;
class A {
public:
int Func1( B& b );
private:
int Func2( B& b );
};
class B {
private:
int _b;
https://riptutorial.com/es/home 562
Capítulo 93: Palabras clave de la declaración
variable
Examples
const
Un especificador de tipo; cuando se aplica a un tipo, produce la versión const-calificada del tipo.
Consulte la palabra clave const para obtener detalles sobre el significado de const .
struct S {
void f();
void g() const;
};
const S s;
s.f(); // error
s.g(); // OK
decltype
C ++ 11
int x = 42;
std::vector<decltype(x)> v(100, x); // v is a vector<int>
struct S {
int x = 42;
};
const S s;
decltype(s.x) y; // y has type int, even though s.x is const
• En todos los demás casos, decltype(e) produce tanto el tipo como la categoría de valor de la
expresión e , como sigue:
https://riptutorial.com/es/home 563
○ Si e es un prvalor de tipo T , entonces decltype(e) es T
C ++ 14
firmado
Una palabra clave que forma parte de ciertos nombres de tipo entero.
• Cuando se usa solo, int está implícito, de modo que signed , signed int e int son del mismo
tipo.
• Cuando se combina con char , produce el tipo signed char , que es un tipo diferente de char ,
incluso si char también está firmado. signed char tiene un rango que incluye al menos -127 a
+127, ambos inclusive.
• Cuando se combina con short , long o long long , es redundante, ya que esos tipos ya están
firmados.
• signed no se puede combinar con bool , wchar_t , char16_t o char32_t .
Ejemplo:
no firmado
• Cuando se usa solo, int está implícito, por lo que unsigned es del mismo tipo que unsigned
int .
• El tipo unsigned char es diferente del tipo char , incluso si char no está firmado. Puede
contener enteros hasta al menos 255.
https://riptutorial.com/es/home 564
• unsigned también se puede combinar con short , long o long long . No se puede combinar
con bool , wchar_t , char16_t o char32_t .
Ejemplo:
char invert_case_table[256] = { ..., 'a', 'b', 'c', ..., 'A', 'B', 'C', ... };
char invert_case(char c) {
unsigned char index = c;
return invert_case_table[index];
// note: returning invert_case_table[c] directly does the
// wrong thing on implementations where char is a signed type
}
volátil
Un calificador de tipo; cuando se aplica a un tipo, produce la versión calificada volátil del tipo. La
calificación volátil desempeña el mismo papel que la calificación const en el sistema de tipos, pero
la volatile no impide que se modifiquen los objetos; en cambio, obliga al compilador a tratar todos
los accesos a tales objetos como efectos secundarios.
https://riptutorial.com/es/home 565
Capítulo 94: Palabras clave de tipo básico
Examples
En t
Indica un tipo entero con signo con "el tamaño natural sugerido por la arquitectura del entorno de
ejecución", cuyo rango incluye al menos -32767 a +32767, ambos inclusive.
int x = 2;
int y = 3;
int z = x + y;
Se puede combinar con unsigned , short , long y long long (qv) para obtener otros tipos de enteros.
bool
bool is_even(int x) {
return x%2 == 0;
}
const bool b = is_even(47); // false
carbonizarse
Un tipo entero que es "lo suficientemente grande como para almacenar cualquier miembro del
conjunto de caracteres básico de la implementación". Está definido por la implementación si char
está firmado (y tiene un rango de al menos -127 a +127, inclusive) o no firmado (y tiene un rango
de al menos 0 a 255, inclusive).
char16_t
C ++ 11
Un tipo entero sin signo con el mismo tamaño y alineación que uint_least16_t , que por lo tanto es
lo suficientemente grande como para contener una unidad de código UTF-16.
https://riptutorial.com/es/home 566
char32_t
C ++ 11
Un tipo entero sin signo con el mismo tamaño y alineación que uint_least32_t , que por lo tanto es
lo suficientemente grande como para contener una unidad de código UTF-32.
flotador
Un tipo de punto flotante. Tiene el rango más estrecho de los tres tipos de punto flotante en C ++.
doble
Un tipo de punto flotante. Su gama incluye la de float . Cuando se combina con long , denota el
tipo de punto flotante long double , cuyo rango incluye el de double .
largo
Indica un tipo entero con signo que es al menos tan largo como int , y cuyo rango incluye al
menos -2147483647 a +2147483647, inclusive (es decir, - (2 ^ 31 - 1) a + (2 ^ 31 - 1)). Este tipo
también se puede escribir como long int .
La combinación long double denota un tipo de punto flotante, que tiene el rango más amplio de los
tres tipos de punto flotante.
C ++ 11
Cuando el especificador long aparece dos veces, como en long long , indica un tipo entero con
signo que es al menos tan largo como long , y cuyo rango incluye al menos -
https://riptutorial.com/es/home 567
9223372036854775807 a +9223372036854775807, inclusive (es decir, - (2 ^ 63 - 1) a + (2 ^ 63 -
1)).
corto
Indica un tipo entero con signo que es al menos tan largo como char , y cuyo rango incluye al
menos -32767 a +32767, inclusive. Este tipo también se puede escribir como short int .
vacío
Un tipo incompleto; no es posible que un objeto tenga el tipo void , ni existen matrices de void o
referencias a void . Se utiliza como el tipo de retorno de funciones que no devuelven nada.
Además, una función se puede declarar de forma redundante con un único parámetro de tipo void
; esto es equivalente a declarar una función sin parámetros (por ejemplo, int main() y int
main(void) declaran la misma función). Esta sintaxis está permitida para la compatibilidad con C
(donde las declaraciones de funciones tienen un significado diferente al de C ++).
El tipo void* ("pointer to void ") tiene la propiedad de que cualquier puntero de objeto se puede
convertir en él y viceversa y dar como resultado el mismo puntero. Esta función hace que el tipo
void* adecuado para ciertos tipos de interfaces de borrado de tipo (tipo inseguro), por ejemplo,
para contextos genéricos en API de estilo C (por ejemplo, qsort , pthread_create ).
Cualquier expresión se puede convertir en una expresión de tipo void ; esto se llama una
expresión de valor descartado :
Esto puede ser útil para señalar explícitamente que el valor de una expresión no es de interés y
que la expresión debe evaluarse solo por sus efectos secundarios.
wchar_t
Un tipo entero lo suficientemente grande como para representar todos los caracteres del conjunto
de caracteres extendido soportado más grande, también conocido como el conjunto de caracteres
anchos. (No es portátil suponer que wchar_t usa alguna codificación en particular, como UTF-16).
Normalmente se usa cuando necesita almacenar caracteres sobre ASCII 255, ya que tiene un
tamaño mayor que el del tipo de carácter char .
https://riptutorial.com/es/home 568
const wchar_t message_ahmaric[] = L"ሰላም ልዑል\n"; //Ahmaric for "hello, world\n"
const wchar_t message_chinese[] = L" \n";// Chinese for "hello, world\n"
const wchar_t message_hebrew[] = L"\םלוע םולשn"; //Hebrew for "hello, world\n"
const wchar_t message_russian[] = L"Привет мир\n"; //Russian for "hello, world\n"
const wchar_t message_tamil[] = L"ஹலோ உலகம்\n"; //Tamil for "hello, world\n"
https://riptutorial.com/es/home 569
Capítulo 95: Paquetes de parametros
Examples
Una plantilla con un paquete de parámetros.
El patrón parameter_pack ... se expande en una lista de sustituciones separadas por comas de
parameter_pack con cada uno de sus parámetros.
1
2
3
hello
https://riptutorial.com/es/home 570
Capítulo 96: Patrón de diseño Singleton
Observaciones
Un Singleton está diseñado para garantizar que una clase solo tenga una instancia y le brinde un
punto de acceso global. Si solo necesita una instancia o un punto de acceso global conveniente,
pero no ambos, considere otras opciones antes de pasar al singleton.
Las variables globales pueden hacer que sea más difícil razonar sobre el código. Por ejemplo, si
una de las funciones de llamada no está contenta con los datos que recibe de un Singleton, ahora
tiene que rastrear qué es lo que primero está dando los datos erróneos de Singleton en primer
lugar.
Los singletons también fomentan el acoplamiento , un término usado para describir dos
componentes del código que se unen, reduciendo así la medida de autocontención de cada
componente.
Singletons no son compatibles con la concurrencia. Cuando una clase tiene un punto de acceso
global, cada subproceso tiene la capacidad de acceder, lo que puede provocar puntos muertos y
condiciones de carrera.
Examples
Inicialización perezosa
Vea este artículo para un diseño simple para un perezoso evaluado con singleton de destrucción
garantizada:
¿Puede alguien proporcionarme una muestra de Singleton en c ++?
class S
{
public:
static S& getInstance()
{
static S instance; // Guaranteed to be destroyed.
https://riptutorial.com/es/home 571
// Instantiated on first use.
return instance;
}
private:
S() {}; // Constructor? (the {} brackets) are needed here.
// C++ 03
// ========
// Dont forget to declare these two. You want to make sure they
// are unacceptable otherwise you may accidentally get copies of
// your singleton appearing.
S(S const&); // Don't Implement
void operator=(S const&); // Don't implement
// C++ 11
// =======
// We can use the better technique of deleting the methods
// we don't want.
public:
S(S const&) = delete;
void operator=(S const&) = delete;
Vea estos dos artículos sobre el orden de inicialización y cómo hacer frente:
Orden de inicialización de variables estáticas
Encontrar problemas de orden de inicialización estática de C ++
Vea este artículo que discute algunas implicaciones de subprocesos para singletons:
Instancia Singleton declarada como variable estática del método GetInstance
Vea este artículo que explica por qué el bloqueo de doble comprobación no funcionará en C ++:
¿Cuáles son todas las conductas indefinidas comunes que un programador de C ++ debe
conocer?
Subclases
class API
{
public:
static API& instance();
virtual ~API() {}
https://riptutorial.com/es/home 572
virtual const char* func1() = 0;
virtual void func2() = 0;
protected:
API() {}
API(const API&) = delete;
API& operator=(const API&) = delete;
};
API& API::instance() {
#if PLATFORM == WIN32
static WindowsAPI instance;
#elif PLATFORM = LINUX
static LinuxAPI instance;
#endif
return instance;
}
C ++ 11
class Foo
{
public:
static Foo& instance()
{
static Foo inst;
return inst;
}
private:
Foo() {}
Foo(const Foo&) = delete;
Foo& operator =(const Foo&) = delete;
};
https://riptutorial.com/es/home 573
Desinticialización estática segura de singleton.
Hay veces con múltiples objetos estáticos en los que necesita poder garantizar que el singleton
no se destruirá hasta que todos los objetos estáticos que usan el singleton ya no lo necesiten.
En este caso, std::shared_ptr puede usarse para mantener el singleton activo para todos los
usuarios, incluso cuando se llama a los destructores estáticos al final del programa:
class Singleton
{
public:
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
private:
Singleton() {}
};
NOTA: Este ejemplo aparece como una respuesta en la sección de preguntas y respuestas aquí.
https://riptutorial.com/es/home 574
Capítulo 97: Patrón de Plantilla Curiosamente
Recurrente (CRTP)
Introducción
Un patrón en el que una clase hereda de una plantilla de clase consigo misma como uno de sus
parámetros de plantilla. CRTP se usa generalmente para proporcionar polimorfismo estático en C
++.
Examples
El patrón de plantilla curiosamente recurrente (CRTP)
CRTP es una poderosa alternativa estática a las funciones virtuales y la herencia tradicional que
se puede usar para dar propiedades de tipos en tiempo de compilación. Funciona al tener una
plantilla de clase base que toma, como uno de sus parámetros de plantilla, la clase derivada. Esto
permite que se realice legalmente una static_cast de su this puntero a la clase derivada.
Por supuesto, esto también significa que una clase CRTP siempre debe usarse como la clase
base de alguna otra clase. Y la clase derivada debe pasar a la clase base.
C ++ 14
Supongamos que tiene un conjunto de contenedores que admiten las funciones begin() y end() .
Los requisitos de la biblioteca estándar para contenedores requieren más funcionalidad. Podemos
diseñar una clase base CRTP que proporcione esa funcionalidad, basada únicamente en begin()
y end() :
#include <iterator>
template <typename Sub>
class Container {
private:
// self() yields a reference to the derived type
Sub& self() { return *static_cast<Sub*>(this); }
Sub const& self() const { return *static_cast<Sub const*>(this); }
public:
decltype(auto) front() {
return *self().begin();
}
decltype(auto) back() {
return *std::prev(self().end());
}
https://riptutorial.com/es/home 575
decltype(auto) operator[](std::size_t i) {
return *std::next(self().begin(), i);
}
};
La clase anterior proporciona las funciones front() , back() , size() y operator[] para cualquier
subclase que proporcione begin() y end() . Un ejemplo de subclase es una matriz simple asignada
dinámicamente:
#include <memory>
// A dynamically allocated array
template <typename T>
class DynArray : public Container<DynArray<T>> {
public:
using Base = Container<DynArray<T>>;
DynArray(std::size_t size)
: size_{size},
data_{std::make_unique<T[]>(size_)}
{ }
private:
std::size_t size_;
std::unique_ptr<T[]> data_;
};
Los usuarios de la clase DynArray pueden usar las interfaces proporcionadas por la clase base
CRTP de la siguiente manera:
DynArray<int> arr(10);
arr.front() = 2;
arr[2] = 5;
assert(arr.size() == 10);
Utilidad: este patrón evita particularmente las llamadas a funciones virtuales en tiempo de
ejecución que ocurren para atravesar la jerarquía de herencia y simplemente se basan en
conversiones estáticas:
DynArray<int> arr(10);
DynArray<int>::Base & base = arr;
base.begin(); // no virtual calls
Limitaciones: debido a que la clase base tiene una plantilla y es diferente para dos DynArray s
diferentes, no es posible almacenar los punteros a sus clases base en una matriz de tipo
https://riptutorial.com/es/home 576
homogéneo como se podría hacer con la herencia normal, donde la clase base no depende de la
derivada tipo:
class A {};
class B: public A{};
A* a = new B;
struct IShape
{
virtual ~IShape() = default;
Cada tipo secundario de IShape necesita implementar la misma función de la misma manera. Eso
es un montón de mecanografía extra. En su lugar, podemos introducir un nuevo tipo en la
jerarquía que hace esto por nosotros:
https://riptutorial.com/es/home 577
struct Square : IShapeAcceptor<Square>
{
Square(const Point& topLeft, double sideLength) : topLeft(topLeft), sideLength(sideLength)
{}
Point topLeft;
double sideLength;
};
https://riptutorial.com/es/home 578
Capítulo 98: Perfilado
Examples
Perfilando con gcc y gprof
El perfilador gprof de GNU, gprof , le permite crear un perfil de su código. Para usarlo, necesitas
realizar los siguientes pasos:
Para construir la aplicación con configuraciones para generar información de perfiles, agregamos
la -pg . Así, por ejemplo, podríamos usar
Etcétera.
Una vez que la aplicación, por ejemplo app , es construido, ejecutarlo como de costumbre:
$ ./app
Etcétera.
El resultado del último comando debe ser una tabla, cuyas filas son las funciones y cuyas
columnas indican el número de llamadas, el tiempo total empleado, el tiempo empleado (es decir,
https://riptutorial.com/es/home 579
el tiempo empleado en la función, excluyendo las llamadas a los hijos).
Para aplicaciones más complejas, los perfiles de ejecución planos pueden ser difíciles de seguir.
Esta es la razón por la que muchas herramientas de creación de perfiles también generan algún
tipo de información anotada del gráfico de llamadas.
gperf2dot convierte la salida de texto de muchos perfiladores (Linux perf, callgrind, oprofile, etc.)
en un diagrama de callgraph. Puede usarlo ejecutando su generador de perfiles (ejemplo para
gprof ):
https://riptutorial.com/es/home 580
Perfilando el uso de la CPU con gcc y Google Perf Tools
https://riptutorial.com/es/home 581
Google Perf Tools también proporciona un perfilador de CPU, con una interfaz un poco más
amigable. Para usarlo:
Por ejemplo:
# compile code
g++ -O3 -std=c++11 main.cpp -o main
dónde:
https://riptutorial.com/es/home 582
2.19/stdlib/random.c:296
1 1.5% 97.0% 1 1.5% __random_r /build/eglibc-3GlaMS/eglibc-
2.19/stdlib/random_r.c:371
1 1.5% 98.5% 1 1.5% __random_r /build/eglibc-3GlaMS/eglibc-
2.19/stdlib/random_r.c:381
1 1.5% 100.0% 1 1.5% rand /build/eglibc-3GlaMS/eglibc-2.19/stdlib/rand.c:28
0 0.0% 100.0% 67 100.0% __libc_start_main /build/eglibc-3GlaMS/eglibc-
2.19/csu/libc-start.c:287
0 0.0% 100.0% 67 100.0% _start ??:0
0 0.0% 100.0% 67 100.0% main ??:0
0 0.0% 100.0% 14 20.9% rand /build/eglibc-3GlaMS/eglibc-2.19/stdlib/rand.c:27
0 0.0% 100.0% 27 40.3% std::vector::_M_emplace_back_aux ??:0
https://riptutorial.com/es/home 583
Capítulo 99: Plantillas
Introducción
Las clases, funciones y (desde C ++ 14) las variables pueden tener plantillas. Una plantilla es un
fragmento de código con algunos parámetros libres que se convertirán en una clase, función o
variable concreta cuando se especifiquen todos los parámetros. Los parámetros pueden ser tipos,
valores o plantillas. Una plantilla conocida es std::vector , que se convierte en un tipo de
contenedor concreto cuando se especifica el tipo de elemento, por ejemplo, std::vector<int> .
Sintaxis
• declaración de plantilla < plantilla-lista-parámetros >
• exportar plantilla < lista de parámetros de plantilla > declaración / * hasta C ++ 11 * /
• plantilla <> declaración
• declaración de plantilla
• Declaración de plantilla externa / * desde C ++ 11 * /
• plantilla < plantilla-lista-parámetros > clase ... ( opt ) identificador ( opt )
• plantilla < plantilla-lista-parámetros > identificador de clase ( opt ) = id-expresión
• template < template-parameters-list > typename ... ( opt ) identifier ( opt ) / * desde C ++ 17 *
/
• plantilla < plantilla-lista-parámetros > identificador de nombre de tipo ( opt ) = id-expresión / *
desde C ++ 17 * /
• postfix-expresión . expresión- plantilla de la plantilla
• postfix-expresión -> plantilla id-expresión
• template especificador de nombre anidado simple-template-id ::
Observaciones
La palabra template es una palabra clave con cinco significados diferentes en el lenguaje C ++,
dependiendo del contexto.
1. Cuando sigue una lista de parámetros de plantilla incluida en <> , declara una plantilla como
una plantilla de clase , una plantilla de función o una especialización parcial de una plantilla
existente.
2. Cuando es seguido por un vacío <> , declara una especialización explícita (completa) .
https://riptutorial.com/es/home 584
void print(const char* s) {
// output the content of the string
printf("%s\n", s);
}
3. Cuando sigue una declaración sin <> , forma una declaración o definición de instanciación
explícita .
template std::set<int> make_singleton(int x); // <-- keyword used in this sense here
struct Allocator {
template <class T>
T* allocate();
};
Antes de C ++ 11, se podría declarar una plantilla con la palabra clave de export , convirtiéndola
en una plantilla exportada . La definición de una plantilla exportada no necesita estar presente en
https://riptutorial.com/es/home 585
cada unidad de traducción en la que se crea una instancia de la plantilla. Por ejemplo, se suponía
que funcionaba lo siguiente:
foo.h :
#ifndef FOO_H
#define FOO_H
export template <class T> T identity(T x);
#endif
foo.cpp :
#include "foo.h"
template <class T> T identity(T x) { return x; }
main.cpp :
#include "foo.h"
int main() {
const int x = identity(42); // x is 42
}
Examples
Plantillas de funciones
Las plantillas también se pueden aplicar a las funciones (así como a las estructuras más
tradicionales) con el mismo efecto.
Esto puede ser usado de la misma manera que las plantillas de estructura.
printSum<int>(4, 5);
printSum<float>(4.5f, 8.9f);
En ambos casos, el argumento de la plantilla se utiliza para reemplazar los tipos de parámetros;
https://riptutorial.com/es/home 586
el resultado funciona igual que una función normal de C ++ (si los parámetros no coinciden con el
tipo de plantilla, el compilador aplica las conversiones estándar).
Una propiedad adicional de las funciones de plantilla (a diferencia de las clases de plantilla) es
que el compilador puede inferir los parámetros de la plantilla en función de los parámetros
pasados a la función.
printSum(5.0, 4); // In this case the parameters are two different types.
// The compiler is unable to deduce the type of T
// because there are contradictions. As a result
// this is a compile time error.
Esta característica nos permite simplificar el código cuando combinamos estructuras de plantillas
y funciones. Hay un patrón común en la biblioteca estándar que nos permite crear una template
structure X mediante la función auxiliar make_X() .
auto val1 = MyPair<int, float>{5, 8.7}; // Create object explicitly defining the types
auto val2 = make_MyPair(5, 8.7); // Create object using the types of the paramters.
// In this code both val1 and val2 are the same
// type.
Nota: Esto no está diseñado para acortar el código. Esto está diseñado para hacer que el código
sea más robusto. Permite cambiar los tipos cambiando el código en un solo lugar en lugar de en
varias ubicaciones.
Reenvío de argumentos
La plantilla puede aceptar tanto las referencias lvalue como rvalue usando la referencia de
reenvío :
https://riptutorial.com/es/home 587
template <typename T>
void f(T &&t);
struct X { };
X x;
f(x); // calls f<X&>(x)
f(X()); // calls f<X>(x)
Nota: vale la pena notar que en el primer caso, decltype(t) es lo mismo que T , pero no en el
segundo.
Para reenviar perfectamente t a otra función, ya sea una referencia de lvalue o rvalue, se debe
usar std::forward :
Nota: las referencias de reenvío solo se pueden usar para los parámetros de la plantilla, por
ejemplo, en el siguiente código, v es una referencia de valor, no una referencia de reenvío:
#include <vector>
La idea básica de una plantilla de clase es que el parámetro de plantilla se sustituye por un tipo
en tiempo de compilación. El resultado es que la misma clase se puede reutilizar para varios
tipos. El usuario especifica qué tipo se utilizará cuando se declara una variable de la clase. Tres
ejemplos de esto se muestran en main() :
#include <iostream>
using std::cout;
https://riptutorial.com/es/home 588
template <typename T> // A simple class to hold one number of any type
class Number {
public:
void setNum(T n); // Sets the class field to the given number
T plus1() const; // returns class field's "follower"
private:
T num; // Class field
};
template <typename T> // Set the class field to the given number
void Number<T>::setNum(T n) {
num = n;
}
int main() {
Number<int> anInt; // Test with an integer (int replaces T in the class)
anInt.setNum(1);
cout << "My integer + 1 is " << anInt.plus1() << "\n"; // Prints 2
Especialización en plantillas
Puede definir la implementación para instancias específicas de una clase de plantilla / método.
template<>
int sqrt<int>(int i) { /* Highly optimized integer implementation */ }
Luego, un usuario que escribe sqrt(4.0) obtendrá la implementación genérica, mientras que
sqrt(4) obtendrá la implementación especializada.
https://riptutorial.com/es/home 589
A diferencia de una especialización de plantilla completa, la especialización de plantilla parcial
permite introducir una plantilla con algunos de los argumentos de la plantilla existente corregidos.
La especialización de plantilla parcial solo está disponible para la clase de plantilla / estructuras:
// Common case:
template<typename T, typename U>
struct S {
T t_val;
U u_val;
};
Como se muestra arriba, las especializaciones de plantillas parciales pueden introducir conjuntos
de datos y funciones completamente diferentes.
template<typename V>
struct S<int, double, V> {
static void foo() {
std::cout << "T = int, U = double\n";
}
};
imprimirá
General case
https://riptutorial.com/es/home 590
T = int
T = int, U = double
// OK.
template<>
void foo<int, int>(int a1, int a2) {
std::cout << "Two ints: " << a1 << " " << a2 << std::endl;
}
void invoke_foo() {
foo(1, 2.1); // Prints "General case: 1 2.1"
foo(1,2); // Prints "Two ints: 1 2"
}
Al igual que en el caso de los argumentos de la función, los parámetros de la plantilla pueden
tener sus valores predeterminados. Todos los parámetros de plantilla con un valor
predeterminado deben declararse al final de la lista de parámetros de plantilla. La idea básica es
que los parámetros de la plantilla con el valor predeterminado se pueden omitir mientras se crea
una instancia de la plantilla.
int main() {
/* Default parameter is ignored, N = 5 */
my_array<int, 5> a;
https://riptutorial.com/es/home 591
Plantilla alias
C ++ 11
Ejemplo básico:
Las plantillas de alias no pueden ser especializadas. Sin embargo, esa funcionalidad se puede
obtener indirectamente haciendo que se refieran a un tipo anidado en una estructura:
template<typename T>
struct nonconst_pointer_helper { typedef T* type; };
template<typename T>
struct nonconst_pointer_helper<T const> { typedef T* type; };
A veces nos gustaría pasar a la plantilla un tipo de plantilla sin fijar sus valores. Para esto se
crean los parámetros de plantilla de plantilla. Ejemplos de parámetros de plantillas de plantillas
muy simples:
int main() {
IntTag<Tag1>::type t;
}
C ++ 11
#include <vector>
#include <iostream>
https://riptutorial.com/es/home 592
}
int main() {
std::vector<float> vf = {1.2, 2.6, 3.7};
auto vi = cast_all<int>(vf);
for(auto &&i: vi) {
std::cout << i << std::endl;
}
}
Antes de C ++ 17, al escribir un parámetro de no tipo de plantilla, primero tenía que especificar su
tipo. Así que un patrón común se convirtió en algo como:
Pero para expresiones complicadas, usar algo como esto implica tener que escribir
decltype(expr), expr al crear instancias de plantillas. La solución es simplificar este lenguaje y
simplemente permitir el auto :
C ++ 17
Un buen ejemplo de motivación puede venir al tratar de combinar la optimización de la base vacía
con un eliminador personalizado para unique_ptr . Los diferentes borradores de la API de C tienen
diferentes tipos de retorno, pero no nos importa, solo queremos que funcione para cualquier
función:
https://riptutorial.com/es/home 593
using unique_ptr_deleter = std::unique_ptr<T, FunctionDeleter<DeleteFn>>;
Y ahora puede simplemente usar cualquier puntero de función que pueda tomar un argumento de
tipo T como un parámetro no tipo de plantilla, independientemente del tipo de retorno, y obtener
una sobrecarga de tamaño unique_ptr fuera de él:
unique_ptr_deleter<std::FILE, std::fclose> p;
Aparte de los tipos como parámetro de plantilla, podemos declarar valores de expresiones
constantes que cumplan uno de los siguientes criterios:
Al igual que todos los parámetros de la plantilla, los parámetros de la plantilla que no son de tipo
se pueden especificar, predeterminar o derivar de manera explícita mediante la deducción de
argumentos de la plantilla.
#include <iostream>
int main()
{
char anArrayOfChar[15];
std::cout << "anArrayOfChar: " << size_of(anArrayOfChar) << "\n";
#include <array>
int main ()
{
std::array<int, 5> foo; // int is a type parameter, 5 is non-type
}
Los parámetros de plantilla que no son de tipo son una de las formas de lograr la recurrencia de
https://riptutorial.com/es/home 594
la plantilla y permiten realizar la metaprogramación .
C ++ 14
A menudo es útil definir clases o estructuras que tienen un número variable y un tipo de miembros
de datos que se definen en el momento de la compilación. El ejemplo canónico es std::tuple ,
pero a veces es necesario definir sus propias estructuras personalizadas. Aquí hay un ejemplo
que define la estructura usando la composición (en lugar de la herencia como con std::tuple .
Comience con la definición general (vacía), que también sirve como el caso base para la
terminación de recrusión en la especialización posterior)
Esto ya nos permite definir una estructura vacía, DataStructure<> data , aunque aún no es muy útil.
T first;
DataStructure<Rest ... > rest;
};
Ahora es suficiente para que DataStructure<int, float, std::string> data(1, 2.1, "hello")
estructuras de datos arbitrarias, como DataStructure<int, float, std::string> data(1, 2.1,
"hello") .
Entonces, ¿qué está pasando? Primero, tenga en cuenta que esta es una especialización cuyo
requisito es que exista al menos un parámetro de plantilla variable (a saber, T arriba), mientras
que no se preocupa por la composición específica del paquete Rest . Saber que T existe permite la
definición de su miembro de datos, first . El resto de los datos se empaquetan recursivamente
como DataStructure<Rest ... > rest . El constructor inicia ambos miembros, incluida una llamada
de constructor recursiva al miembro rest .
Para comprender mejor esto, podemos trabajar con un ejemplo: supongamos que tiene una
declaración DataStructure<int, float> data . La declaración primero coincide con la
especialización, produciendo una estructura con int first y DataStructure<float> rest de
miembros de datos. La definición de rest coincide nuevamente con esta especialización, creando
float first su propia float first y los DataStructure<> rest . Finalmente, este último rest coincide
con la definición del caso base, produciendo una estructura vacía.
https://riptutorial.com/es/home 595
Puedes visualizar esto de la siguiente manera:
DataStructure<int, float>
-> int first
-> DataStructure<float> rest
-> float first
-> DataStructure<> rest
-> (empty)
Ahora tenemos la estructura de datos, pero no es terriblemente útil todavía, ya que no podemos
acceder fácilmente a los elementos de datos individuales (por ejemplo, para acceder al último
miembro de DataStructure<int, float, std::string> data tendríamos que usar
data.rest.rest.first , que no es exactamente fácil de usar). Así que le agregamos un método de
get (solo necesario en la especialización, ya que la estructura del caso base no tiene datos para
get ):
Como puede ver, esta función de get miembros tiene su propia plantilla, esta vez en el índice del
miembro que se necesita (de modo que el uso puede ser similar a data.get<1>() , similar a
std::tuple ). El trabajo real se realiza mediante una función estática en una clase auxiliar,
GetHelper . La razón por la que no podemos definir la funcionalidad requerida directamente en
DataStructure 's get es porque (como veremos dentro de poco) que tendría que especializarse en
idx - pero no es posible especializarse una función miembro de plantilla sin que se especializa la
clase que contiene modelo. Tenga en cuenta que el uso de un auto estilo C ++ 14 aquí hace que
nuestras vidas sean mucho más simples, ya que de lo contrario necesitaríamos una expresión
bastante complicada para el tipo de retorno.
Así que a la clase de ayuda. Esta vez necesitaremos una declaración a futuro vacía y dos
especializaciones. Primero la declaración:
Ahora el caso base (cuando idx==0 ). En este caso acabamos de devolver el first miembro:
https://riptutorial.com/es/home 596
}
};
Para ver un ejemplo, supongamos que tenemos DataStructure<int, float> data y necesitamos
data.get<1>() . Esto invoca a GetHelper<1, DataStructure<int, float>>::get(data) (la segunda
especialización), que a su vez invoca a GetHelper<0, DataStructure<float>>::get(data.rest) , que
finalmente devuelve (por la 1ª especialización como ahora idx es 0) data.rest.first .
¡Eso es todo! Aquí está el código de funcionamiento completo, con algunos ejemplos de uso en la
función main :
#include <iostream>
T first;
DataStructure<Rest ... > rest;
template<size_t idx>
auto get()
{
return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
}
};
https://riptutorial.com/es/home 597
};
int main()
{
DataStructure<int, float, std::string> data(1, 2.1, "Hello");
return 0;
}
Instanciación explícita
Una definición de creación de instancias explícita crea y declara una clase, función o variable
concreta de una plantilla, sin usarla todavía. Una instanciación explícita puede ser referenciada
desde otras unidades de traducción. Esto se puede usar para evitar definir una plantilla en un
archivo de encabezado, si solo se creará una instancia con un conjunto finito de argumentos. Por
ejemplo:
// print_string.h
template <class T>
void print_string(const T* str);
// print_string.cpp
#include "print_string.h"
template void print_string(const char*);
template void print_string(const wchar_t*);
C ++ 11
Si una definición de creación de instancias explícita va precedida por la palabra clave extern , se
convierte en una declaración de creación de instancias explícita. La presencia de una declaración
de instanciación explícita para una especialización dada evita la creación de instancias implícita
de la especialización dada dentro de la unidad de traducción actual. En cambio, una referencia a
esa especialización que de otro modo causaría una creación de instancias implícita puede
referirse a una definición de creación de instancias explícita en la misma u otra TU.
https://riptutorial.com/es/home 598
foo.h
#ifndef FOO_H
#define FOO_H
template <class T> void foo(T x) {
// complicated implementation
}
#endif
foo.cpp
#include "foo.h"
// explicit instantiation definitions for common cases
template void foo(int);
template void foo(double);
main.cpp
#include "foo.h"
// we already know foo.cpp has explicit instantiation definitions for these
extern template void foo(double);
int main() {
foo(42); // instantiates foo<int> here;
// wasteful since foo.cpp provides an explicit instantiation already!
foo(3.14); // does not instantiate foo<double> here;
// uses instantiation of foo<double> in foo.cpp instead
}
https://riptutorial.com/es/home 599
Capítulo 100: Plantillas de expresiones
Examples
Plantillas de expresiones básicas en expresiones algebraicas de elementos
Introducción y motivación.
Las plantillas de expresión (indicadas como ETs a continuación) son una poderosa técnica de
metaprogramación de plantilla, utilizada para acelerar los cálculos de expresiones a veces
bastante caras. Se usa ampliamente en diferentes dominios, por ejemplo, en la implementación
de bibliotecas de álgebra lineal.
Para este ejemplo, considere el contexto de los cálculos algebraicos lineales. Más
específicamente, los cálculos que involucran solo operaciones de elementos . Este tipo de
cálculos son las aplicaciones más básicas de los ET y sirven como una buena introducción a
cómo funcionan los ET internamente.
En aras de la simplicidad, asumiré que la clase Vector y operation + (vector plus: element-wise
plus operation) y operation * (aquí significa vector producto interno: también element-operation
operation) se implementan correctamente, como cómo deberían ser, matemáticamente.
También habrá cuatro construcciones de instancias de Vector en total: vec_1, vec_2, vec_3 y
result
https://riptutorial.com/es/home 600
. En otras palabras, en este ejemplo, donde solo están involucradas operaciones de
elementos , se garantiza que no se crearán objetos temporales a partir de cálculos
intermedios .
https://riptutorial.com/es/home 601
/ / \
/ / \
vec_1 vec_2 vec_3
• El cálculo final se implementa mirando a través de la jerarquía del gráfico : ya que aquí
se tratan solo operaciones de elementos , el cálculo de cada valor indexado en el result se
puede realizar de forma independiente : la evaluación final del result se puede posponer
perezosamente a un elemento Evaluación sabia de cada elemento del result . En otras
palabras, dado que el cálculo de un elemento de result , elem_res , se puede expresar
utilizando los elementos correspondientes en vec_1 ( elem_1 ), vec_2 ( elem_2 ) y vec_3 ( elem_3
) como:
por lo tanto, no es necesario crear un Vector temporal para almacenar el resultado del producto
interno intermedio: todo el cómputo de un elemento se puede realizar en conjunto y se
puede codificar dentro de la operación de acceso indexado .
#ifndef EXPR_VEC
# define EXPR_VEC
# include <vector>
# include <cassert>
# include <utility>
# include <iostream>
# include <algorithm>
# include <functional>
///
/// This is a wrapper for std::vector. It's only purpose is to print out a log when a
/// vector constructions in called.
/// It wraps the indexed access operator [] and the size() method, which are
/// important for later ETs implementation.
///
// std::vector wrapper.
template<typename ScalarType> class Vector
{
public:
explicit Vector() { std::cout << "ctor called.\n"; };
explicit Vector(int size): _vec(size) { std::cout << "ctor called.\n"; };
explicit Vector(const std::vector<ScalarType> &vec): _vec(vec)
{ std::cout << "ctor called.\n"; };
https://riptutorial.com/es/home 602
{ std::cout << "copy ctor called.\n"; };
Vector(Vector<ScalarType> && vec): _vec(std::move(vec()))
{ std::cout << "move ctor called.\n"; };
private:
std::vector<ScalarType> _vec;
};
///
/// These are conventional overloads of operator + (the vector plus operation)
/// and operator * (the vector inner product operation) without using the expression
/// templates. They are later used for bench-marking purpose.
///
std::vector<ScalarType> _vec;
_vec.resize(lhs().size());
std::transform(std::cbegin(lhs()), std::cend(lhs()),
std::cbegin(rhs()), std::begin(_vec),
std::plus<>());
return Vector<ScalarType>(std::move(_vec));
}
std::vector<ScalarType> _vec;
_vec.resize(lhs().size());
std::transform(std::cbegin(lhs()), std::cend(lhs()),
std::cbegin(rhs()), std::begin(_vec),
std::multiplies<>());
return Vector<ScalarType>(std::move(_vec));
}
#endif //!EXPR_VEC
https://riptutorial.com/es/home 603
para operaciones de elementos (vector plus y vector inner
product)
1. La sección 1 implementa una clase base para todas las expresiones. Emplea el patrón de
plantilla curiosamente recurrente ( CRTP ).
2. La Sección 2 implementa el primer PAE : un terminal , que es solo una envoltura
(referencia constante) de una estructura de datos de entrada que contiene un valor de
entrada real para el cálculo.
3. La Sección 3 implementa el segundo PAE : binary_operation , que es una plantilla de
clase que se usará más tarde para vector_plus y vector_innerprod. Está parametrizado por
el tipo de operación , el PAE del lado izquierdo y el PAE del lado derecho . El cálculo
real se codifica en el operador de acceso indexado.
4. La Sección 4 define las operaciones vector_plus y vector_innerprod como una operación de
elementos . También sobrecarga al operador + y * para PAE s: de modo que estas dos
operaciones también devuelven PAE .
#ifndef EXPR_EXPR
# define EXPR_EXPR
namespace expr
{
/// -----------------------------------------
///
/// Section 1.
///
/// The first section is a base class template for all kinds of expression. It
/// employs the Curiously Recurring Template Pattern, which enables its instantiation
/// to any kind of expression structure inheriting from it.
///
/// -----------------------------------------
protected:
explicit expr_base() {};
int size() const { return self().size_impl(); }
auto operator[](int indx) const { return self().at_impl(indx); }
auto operator()() const { return self()(); };
};
https://riptutorial.com/es/home 604
/// -----------------------------------------
///
/// The following section 2 & 3 are abstractions of pure algebraic expressions (PAE).
/// Any PAE can be converted to a real object instance using operator(): it is in
/// this conversion process, where the real computations are done.
///
/// Section 2. Terminal
///
/// A terminal is an abstraction wrapping a const reference to the Vector data
/// structure. It inherits from expr_base, therefore providing a unified interface
/// wrapping a Vector into a PAE.
///
/// It provides the size() method, indexed access through at_impl() and a conversion
/// to referenced object through () operator.
///
/// It might no be necessary for user defined data structures to have a terminal
/// wrapper, since user defined structure can inherit expr_base, therefore eliminates
/// the need to provide such terminal wrapper.
///
/// -----------------------------------------
private:
const DataType &_val;
};
/// -----------------------------------------
///
/// Section 3. Binary operation expression.
///
/// This is a PAE abstraction of any binary expression. Similarly it inherits from
/// expr_base.
///
/// It provides the size() method, indexed access through at_impl() and a conversion
/// to referenced object through () operator. Each call to the at_impl() method is
/// a element wise computation.
///
/// -----------------------------------------
https://riptutorial.com/es/home 605
{
public:
using base_type = expr_base<binary_ops<Ops,lExpr,rExpr>>;
using base_type::size;
using base_type::operator[];
friend base_type;
explicit binary_ops(const Ops &ops, const lExpr &lxpr, const rExpr &rxpr)
: _ops(ops), _lxpr(lxpr), _rxpr(rxpr) {};
int size_impl() const { return _lxpr.size(); };
/// -----------------------------------------
/// Section 4.
///
/// The following two structs defines algebraic operations on PAEs: here only vector
/// plus and vector inner product are implemented.
///
/// First, some element-wise operations are defined : in other words, vec_plus and
/// vec_prod acts on elements in Vectors, but not whole Vectors.
///
/// Then, operator + & * are overloaded on PAEs, such that: + & * operations on PAEs
/// also return PAEs.
///
/// -----------------------------------------
https://riptutorial.com/es/home 606
{ return lhs*rhs; }
};
} //!expr
#endif //!EXPR_EXPR
# include <chrono>
# include <iomanip>
# include <iostream>
# include "vec.hh"
# include "expr.hh"
# include "boost/core/demangle.hpp"
int main()
{
using dtype = float;
constexpr int size = 5e7;
std::vector<dtype> _vec1(size);
std::vector<dtype> _vec2(size);
std::vector<dtype> _vec3(size);
Vector<dtype> vec1(std::move(_vec1));
Vector<dtype> vec2(std::move(_vec2));
Vector<dtype> vec3(std::move(_vec3));
https://riptutorial.com/es/home 607
std::cout << std::setprecision(6) << std::fixed
<< "No-ETs. Time eclapses: " << (stop_ms_no_ets-start_ms_no_ets)/1000.0
<< " s.\n" << std::endl;
expr::terminal<Vector<dtype>> vec4(vec1);
expr::terminal<Vector<dtype>> vec5(vec2);
expr::terminal<Vector<dtype>> vec6(vec3);
return 0;
}
Aquí hay una salida posible cuando se compila con -O3 -std=c++14 usando GCC 5.3:
ctor called.
ctor called.
ctor called.
• El uso de ETs logra un aumento de rendimiento bastante significativo en este caso (> 3x).
• Se elimina la creación de objetos vectoriales temporales. Como en el caso de ETs , ctor es
llamado solo una vez.
• Boost :: demangle se usó para visualizar el tipo de retorno de ET antes de la conversión:
claramente construyó exactamente el mismo gráfico de expresión demostrado
https://riptutorial.com/es/home 608
anteriormente.
Draw-backs y advertencias
• Otra advertencia de usar ETs es que juegan bien con la palabra clave auto . Como se
mencionó anteriormente, los PAE son esencialmente proxies: y los proxies básicamente no
funcionan bien con el auto . Considere el siguiente ejemplo:
Aquí, en cada iteración del bucle for, el resultado se volverá a evaluar , ya que el gráfico de
expresión en lugar del valor calculado se pasa al bucle for.
• boost :: proto es una biblioteca poderosa que te permite definir tus propias reglas y
gramáticas para tus propias expresiones y ejecutar usando ETs .
• Eigen es una biblioteca para álgebra lineal que implementa varios cálculos algebraicos de
manera eficiente utilizando ETs .
Antes de sumergirse realmente en las plantillas de expresión, debe comprender por qué las
necesita en primer lugar. Para ilustrar esto, considere la muy simple clase de Matrix que se da a
https://riptutorial.com/es/home 609
continuación:
private:
std::vector<T> values;
};
Dada la definición de clase anterior, ahora puede escribir expresiones de matriz como:
// initialize a, b & c
for (std::size_t y = 0; y != rows; ++y) {
for (std::size_t x = 0; x != cols; ++x) {
a(x, y) = 1.0;
b(x, y) = 2.0;
c(x, y) = 3.0;
}
}
Como se ilustra arriba, ser capaz de sobrecargar el operator+() proporciona una notación que
imita la notación matemática natural de las matrices.
https://riptutorial.com/es/home 610
Para entender por qué, debe considerar lo que sucede cuando escribe una expresión como Matrix
d = a + b + c . De hecho, esto se expande a ((a + b) + c) u operator+(operator+(a, b), c) . En
otras palabras, el bucle dentro del operator+() se ejecuta dos veces, mientras que podría haberse
realizado fácilmente en una sola pasada. Esto también resulta en la creación de 2 temporarios, lo
que degrada aún más el rendimiento. En esencia, al agregar la flexibilidad para usar una notación
cercana a su contraparte matemática, también ha hecho que la clase Matrix altamente ineficiente.
Por ejemplo, sin la sobrecarga del operador, podría implementar una suma de Matrix mucho más
eficiente utilizando un solo paso:
Sin embargo, el ejemplo anterior tiene sus propias desventajas porque crea una interfaz mucho
más compleja para la clase Matrix (tendrías que considerar métodos como Matrix::add2() ,
Matrix::AddMultiply() etc.).
En su lugar, demos un paso atrás y veamos cómo podemos adaptar la sobrecarga del operador
para que funcione de una manera más eficiente.
Esta es la idea central detrás de las plantillas de expresión: en lugar de que el operator+() evalúe
inmediatamente el resultado de agregar dos instancias de Matrix, devolverá una "plantilla de
expresión" para una futura evaluación una vez que se haya construido todo el árbol de
expresiones.
Por ejemplo, aquí hay una posible implementación para una plantilla de expresión
correspondiente a la suma de 2 tipos:
https://riptutorial.com/es/home 611
value_type operator() (int x, int y) const {
return lhs(x, y) + rhs(x, y);
}
private:
const LHS& lhs;
const RHS& rhs;
};
Como puede ver, el operator+() ya no devuelve una "evaluación impaciente" del resultado de
agregar 2 instancias de Matrix (que sería otra instancia de Matrix), sino una plantilla de expresión
que representa la operación de adición. El punto más importante a tener en cuenta es que la
expresión aún no se ha evaluado. Simplemente contiene referencias a sus operandos.
De hecho, nada le impide crear una instancia de la plantilla de expresión MatrixSum<> siguiente
manera:
Sin embargo, en una etapa posterior, cuando realmente necesite el resultado de la suma, evalúe
la expresión d = a + b siguiente manera:
Como se puede ver, otro de los beneficios de utilizar una plantilla de expresión, es que
básicamente ha logrado evaluar la suma de a y b y asignarlo a d en una sola pasada.
Además, nada le impide combinar varias plantillas de expresión. Por ejemplo, a + b + c daría
como resultado la siguiente plantilla de expresión:
Y aquí nuevamente puedes evaluar el resultado final usando una sola pasada:
https://riptutorial.com/es/home 612
la clase Matrix . Esto se logra esencialmente al proporcionar una implementación para
Matrix::operator=() , que toma la plantilla de expresión como un argumento y la evalúa en una
sola pasada, como lo hizo "manualmente" antes:
private:
std::vector<T> values;
};
https://riptutorial.com/es/home 613
Capítulo 101: Polimorfismo
Examples
Definir clases polimórficas.
El ejemplo típico es una clase de forma abstracta, que luego puede derivarse en cuadrados,
círculos y otras formas concretas.
La clase padre:
class Shape {
public:
virtual ~Shape() = default;
virtual double get_surface() const = 0;
virtual void describe_object() const { std::cout << "this is a shape" << std::endl; }
• No tiene sentido definir get_surface() para una forma abstracta. Es por esto que a la función
le sigue = 0 . Esto significa que la función es pura función virtual .
• Puede definir funciones miembro no virtuales. Cuando estas funciones se invocarán para un
objeto, la función se elegirá en función de la clase utilizada en el momento de la
compilación. Aquí get_double_surface() se define de esta manera.
• Una clase que contiene al menos una función virtual pura es una clase abstracta. Las clases
abstractas no pueden ser instanciadas. Solo puede tener punteros o referencias de un tipo
de clase abstracta.
Clases derivadas
Una vez que se define una clase base polimórfica, se puede derivar. Por ejemplo:
https://riptutorial.com/es/home 614
double side_length;
public:
Square (const Point& top_left, double side)
: top_left(top_left), side_length(side_length) {}
Algunas explicaciones:
• Puede definir o anular cualquiera de las funciones virtuales de la clase principal. El hecho de
que una función fuera virtual en la clase principal la hace virtual en la clase derivada. No hay
necesidad de decirle al compilador la palabra clave virtual nuevo. Pero se recomienda
agregar la override la palabra clave al final de la declaración de la función, a fin de evitar
errores sutiles causados por variaciones inadvertidas en la firma de la función.
• Si se definen todas las funciones virtuales puras de la clase padre, puede crear una
instancia de los objetos para esta clase, de lo contrario, también se convertirá en una clase
abstracta.
• No está obligado a anular todas las funciones virtuales. Puede conservar la versión del
padre si se adapta a sus necesidades.
Ejemplo de instanciación
int main() {
Square square(Point(10.0, 0.0), 6); // we know it's a square, the compiler also
square.describe_object();
std::cout << "Surface: " << square.get_surface() << std::endl;
Shape *ps = nullptr; // we don't know yet the real type of the object
ps = &circle; // it's a circle, but it could as well be a square
ps->describe_object();
std::cout << "Surface: " << ps->get_surface() << std::endl;
}
Descenso seguro
una caída hacia abajo sería desde una Shape polimórfica general hasta una de sus formas
derivadas y más específicas, como Square o Circle .
https://riptutorial.com/es/home 615
La mayoría de las veces, no necesitará saber cuál es el tipo real del objeto, ya que las funciones
virtuales le permiten manipular su objeto independientemente de su tipo:
Sin embargo, es posible que a veces tenga que abatirse. Un ejemplo típico es cuando desea
invocar una función no virtual que existe solo para la clase secundaria.
Consideremos por ejemplo los círculos. Sólo los círculos tienen un diámetro. Así que la clase se
definiría como:
class Circle: public Shape { // for Shape, see example on defining a polymorphic class
Point center;
double radius;
public:
Circle (const Point& center, double radius)
: center(center), radius(radius) {}
La función miembro get_diameter() solo existe para círculos. No se definió para un objeto Shape :
Shape* ps = get_any_shape();
ps->get_diameter(); // OUCH !!! Compilation error
¿Cómo abatir?
Esto hará el truco. Pero es muy arriesgado: si ps aparece por algo más que un Circle el
comportamiento de su código será indefinido.
Así que, en lugar de jugar a la ruleta rusa, debes usar de forma segura un dynamic_cast . Esto es
específicamente para las clases polimórficas:
int main() {
Circle circle(Point(0.0, 0.0), 10);
Shape &shape = circle;
std::cout << "The shape has a surface of " << shape.get_surface() << std::endl;
https://riptutorial.com/es/home 616
if (pc)
std::cout << "The shape is a circle of diameter " << pc->get_diameter() << std::endl;
else
std::cout << "The shape isn't a circle !" << std::endl;
}
Tenga en cuenta que dynamic_cast no es posible en una clase que no sea polimórfica. Necesitaría
al menos una función virtual en la clase o sus padres para poder usarla.
Polimorfismo y Destructores
Si se pretende que una clase se utilice de forma polimórfica, con las instancias derivadas
almacenadas como punteros / referencias base, el destructor de su clase base debe ser virtual o
estar protected . En el primer caso, esto causará que la destrucción del objeto verifique el vtable ,
llamando automáticamente al destructor correcto según el tipo dinámico. En este último caso, la
destrucción del objeto a través de un puntero / referencia de clase base está deshabilitada, y el
objeto solo se puede eliminar cuando se trata explícitamente como su tipo real.
struct VirtualDestructor {
virtual ~VirtualDestructor() = default;
};
struct ProtectedDestructor {
protected:
~ProtectedDestructor() = default;
};
// ...
Ambas prácticas garantizan que siempre se llamará al destructor de la clase derivada en las
instancias de la clase derivada, lo que evita las fugas de memoria.
https://riptutorial.com/es/home 617
Capítulo 102: precedencia del operador
Observaciones
Los operadores se enumeran de arriba a abajo, en precedencia descendente. Los operadores
con el mismo número tienen la misma prioridad y la misma asociatividad.
1. ::
2. Los operadores de postfix: [] () T(...) . -> ++ -- dynamic_cast static_cast reinterpret_cast
const_cast typeid
3. Los operadores de prefijo únicos: ++ -- * & + - ! ~ sizeof new delete delete[] ; la notación de
reparto de estilo C, (T)... ; (C ++ 11 y superior) sizeof... alignof noexcept
4. .* y ->*
5. * , / , y % , operadores aritméticos binarios
6. + y - , operadores aritméticos binarios
7. << y >>
8. < , > , <= , >=
9. == y !=
10. & , el operador bit a bit
11. ^
12. |
13. &&
14. ||
15. ?: (operador condicional ternario)
16. = , *= , /= , %= , += , -= , >>= , <<= , &= , ^= , |=
17. throw
18. , (el operador de coma)
Las reglas para el operador condicional ternario son un poco más complicadas de lo que pueden
expresar las reglas de precedencia simples.
Examples
https://riptutorial.com/es/home 618
Operadores aritméticos
La multiplicación y la división han dejado la asociatividad (lo que significa que se evaluarán de
izquierda a derecha) y tienen mayor prioridad que la suma y la resta, que también tienen la
asociatividad izquierda.
//Addition:
//With Multiplication
Agregar el paréntesis no cambia el comportamiento, sin embargo, hace que sea más fácil de leer.
Al agregar estos paréntesis, no existe confusión sobre la intención del escritor.
&& tiene precedencia sobre ||, esto significa que se colocan paréntesis para evaluar lo que se
evaluaría en conjunto.
https://riptutorial.com/es/home 619
c ++ utiliza la evaluación de cortocircuito en && y || No hacer ejecuciones innecesarias.
Si el lado izquierdo de || devuelve verdadero el lado derecho no necesita ser evaluado nunca
más.
#include <iostream>
#include <string>
int main(){
bool result;
//let's evaluate 3 booleans with || and && to illustrate operator precedence
//precedence does not mean that && will be evaluated first but rather where
//parentheses would be added
//example 1
result =
False("A") || False("B") && False("C");
// eq. False("A") || (False("B") && False("C"))
//FalseA
//FalseB
//"Short-circuit evaluation skip of C"
//A is false so we have to evaluate the right of ||,
//B being false we do not have to evaluate C to know that the result is false
result =
True("A") || False("B") && False("C");
// eq. True("A") || (False("B") && False("C"))
cout << result << " :=====================" << endl;
//TrueA
//"Short-circuit evaluation skip of B"
//"Short-circuit evaluation skip of C"
//A is true so we do not have to evaluate
// the right of || to know that the result is true
//If || had precedence over && the equivalent evaluation would be:
// (True("A") || False("B")) && False("C")
//What would print
//TrueA
//"Short-circuit evaluation skip of B"
//FalseC
//Because the parentheses are placed differently
//the parts that get evaluated are differently
//which makes that the end result in this case would be False because C is false
}
Operadores Unarios
https://riptutorial.com/es/home 620
Los operadores unarios actúan sobre el objeto sobre el que son llamados y tienen una alta
prioridad. (Ver las observaciones)
Cuando se usa postfix, la acción ocurre solo después de que se evalúa la operación completa, lo
que lleva a algunas aritméticas interesantes:
int a = 1;
++a; // result: 2
a--; // result: 1
int minusa=-a; // result: -1
bool b = true;
!b; // result: true
a=4;
int c = a++/2; // equal to: (a==4) 4 / 2 result: 2 ('a' incremented postfix)
cout << a << endl; // prints 5!
int d = ++a/2; // equal to: (a+1) == 6 / 2 result: 3
https://riptutorial.com/es/home 621
Capítulo 103: Preprocesador
Introducción
El preprocesador de C es un simple analizador / sustituto de texto que se ejecuta antes de la
compilación real del código. Usado para extender y facilitar el uso del lenguaje C (y posterior C
++), puede usarse para:
Observaciones
Las instrucciones del preprocesador se ejecutan antes de que los archivos de origen se
entreguen al compilador. Son capaces de lógica condicional de muy bajo nivel. Dado que las
construcciones del preprocesador (p. Ej., Macros similares a objetos) no se escriben como las
funciones normales (el paso de preprocesamiento ocurre antes de la compilación), el compilador
no puede imponer verificaciones de tipos, por lo tanto, deben usarse con cuidado.
Examples
Incluir guardias
Un archivo de encabezado puede ser incluido por otros archivos de encabezado. Por lo tanto, un
archivo fuente (unidad de compilación) que incluye múltiples encabezados puede, indirectamente,
incluir algunos encabezados más de una vez. Si tal archivo de encabezado que se incluye más de
una vez contiene definiciones, el compilador (después del preprocesamiento) detecta una
violación de la Regla de una definición (por ejemplo, §3.2 del estándar C ++ 2003) y, por lo tanto,
emite un diagnóstico y falla la compilación.
La inclusión múltiple se evita utilizando "incluir guardas", que a veces también se conocen como
guardas de encabezado o guardas de macros. Estos se implementan utilizando las directivas
#define , #ifndef , #endif del preprocesador.
p.ej
// Foo.h
#ifndef FOO_H_INCLUDED
#define FOO_H_INCLUDED
https://riptutorial.com/es/home 622
class Foo // a class definition
{
};
#endif
La ventaja clave del uso de incluir guardias es que funcionarán con todos los compiladores y
preprocesadores que cumplen con los estándares.
Sin embargo, incluir protecciones también causa algunos problemas para los desarrolladores, ya
que es necesario asegurarse de que las macros sean únicas en todos los encabezados utilizados
en un proyecto. Específicamente, si dos (o más) encabezados usan FOO_H_INCLUDED como su
protección de inclusión, el primero de esos encabezados incluidos en una unidad de compilación
evitará efectivamente que se incluyan los demás. Se presentan desafíos particulares si un
proyecto usa una cantidad de bibliotecas de terceros con archivos de encabezado que se usan
para incluir guardas en común.
También es necesario asegurarse de que las macros utilizadas en las guardas de inclusión no
entren en conflicto con ninguna otra macros definida en los archivos de encabezado.
// Foo.h
#pragma once
class Foo
{
};
Si bien #pragma once evita algunos problemas asociados con la inclusión de guardias, un #pragma ,
por definición en los estándares, es inherentemente un gancho específico del compilador y será
ignorado silenciosamente por los compiladores que no lo admiten. Los proyectos que usan
#pragma once son más difíciles de trasladar a compiladores que no lo admiten.
https://riptutorial.com/es/home 623
• multiplataforma compila - sola base de código, compilación de múltiples plataformas.
• utilizando una base de código común para múltiples versiones de aplicaciones (por
ejemplo, versiones Basic, Premium y Pro de un software), con características ligeramente
diferentes.
#ifdef _WIN32
#include <windows.h> // and other windows system files
#endif
#include <cstdio>
Las macros como _WIN32 , __APPLE__ o __unix__ normalmente están predefinidas por las
implementaciones correspondientes.
void s_PrintAppStateOnUserPrompt()
{
std::cout << "--------BEGIN-DUMP---------------\n"
<< AppState::Instance()->Settings().ToString() << "\n"
#if ( 1 == TESTING_MODE ) //privacy: we want user details only when testing
<< ListToString(AppState::UndoStack()->GetActionNames())
<< AppState::Instance()->CrntDocument().Name()
<< AppState::Instance()->CrntDocument().SignatureSHA() << "\n"
#endif
<< "--------END-DUMP---------------\n"
}
Ejemplo c: habilite una función premium en una compilación de producto separada (nota: esto es
ilustrativo. A menudo es una mejor idea permitir que una función se desbloquee sin la necesidad
de reinstalar una aplicación)
void MainWindow::OnProcessButtonClick()
{
#ifndef _PREMIUM
CreatePurchaseDialog("Buy App Premium", "This feature is available for our App Premium
users. Click the Buy button to purchase the Premium version at our website");
return;
#endif
//...actual feature logic here
https://riptutorial.com/es/home 624
}
Se puede llamar al preprocesador con símbolos predefinidos (con inicialización opcional). Por
ejemplo, este comando ( gcc -E ejecuta solo el preprocesador)
Si una macro no está definida y su valor se compara o verifica, el preprocesador casi siempre
asume que el valor es 0 . Hay algunas maneras de trabajar con esto. Un enfoque consiste en
asumir que la configuración predeterminada se representa como 0, y cualquier cambio (por
ejemplo, en el perfil de compilación de la aplicación) debe realizarse explícitamente (por ejemplo,
ENABLE_EXTRA_DEBUGGING = 0 por defecto, establecer -DENABLE_EXTRA_DEBUGGING =
1 para anular). Otro enfoque es hacer que todas las definiciones y valores predeterminados sean
explícitos. Esto se puede lograr usando una combinación de #error #ifndef y #error :
#ifndef (ENABLE_EXTRA_DEBUGGING)
// please include DefaultDefines.h if not already included.
# error "ENABLE_EXTRA_DEBUGGING is not defined"
#else
# if ( 1 == ENABLE_EXTRA_DEBUGGING )
//code
# endif
#endif
Macros
Las macros se clasifican en dos grupos principales: macros similares a objetos y macros similares
a funciones. Las macros se tratan como una sustitución de token al principio del proceso de
compilación. Esto significa que grandes secciones (o repetidas) de código se pueden abstraer en
una macro preprocesadora.
https://riptutorial.com/es/home 625
// They can be used like this:
double pi_macro = PI;
double area_macro = AREA(4.6);
La biblioteca Qt utiliza esta técnica para crear un sistema de metaobjetos al hacer que el usuario
declare la macro Q_OBJECT al frente de la clase definida por el usuario que extiende QObject.
Los nombres de macro generalmente se escriben en mayúsculas, para que sean más fáciles de
diferenciar del código normal. Esto no es un requisito, pero muchos programadores lo consideran
un buen estilo.
Cuando se encuentra una macro similar a un objeto, se expande como una simple operación de
copiar y pegar, con el nombre de la macro reemplazado con su definición. Cuando se encuentra
una macro similar a una función, tanto su nombre como sus parámetros se expanden.
Debido a esto, los parámetros de macros similares a funciones a menudo se incluyen entre
paréntesis, como en AREA() anterior. Esto es para evitar cualquier error que pueda ocurrir durante
la expansión de la macro, específicamente los errores causados por un solo parámetro de macro
compuesto por múltiples valores reales.
#define BAD_AREA(r) PI * r * r
También tenga en cuenta que debido a esta simple expansión, se debe tener cuidado con los
parámetros pasados a las macros, para evitar efectos secundarios inesperados. Si el parámetro
se modifica durante la evaluación, se modificará cada vez que se use en la macro expandida, que
generalmente no es lo que queremos. Esto es cierto incluso si la macro incluye los parámetros
entre paréntesis para evitar que la expansión rompa algo.
int oops = 5;
double incremental_damage = AREA(oops++);
// Compiler sees:
double incremental_damage = (3.14159265358979*(oops++)*(oops++));
Además, las macros no proporcionan seguridad de tipos, lo que lleva a errores difíciles de
entender sobre la falta de coincidencia de tipos.
https://riptutorial.com/es/home 626
Como los programadores normalmente terminan las líneas con un punto y coma, las macros que
están diseñadas para usarse como líneas independientes a menudo están diseñadas para
"tragar" un punto y coma; esto evita que cualquier error involuntario sea causado por un punto y
coma adicional.
if (some_condition)
// Oops.
IF_BREAKER(some_func);
else
std::cout << "I am accidentally an orphan." << std::endl;
En este ejemplo, el punto y coma doble inadvertido rompe el bloque if...else , impidiendo que el
compilador haga coincidir el else con el if . Para evitar esto, el punto y coma se omite en la
definición de macro, lo que provocará que se "trague" el punto y coma inmediatamente después
de su uso.
if (some_condition)
IF_FIXER(some_func);
else
std::cout << "Hooray! I work again!" << std::endl;
Dejar el punto y coma final también permite que la macro se use sin terminar la declaración
actual, lo que puede ser beneficioso.
// ...
Normalmente, una definición de macro termina al final de la línea. Sin embargo, si una macro
necesita cubrir varias líneas, se puede usar una barra invertida al final de una línea para indicar
esto. Esta barra invertida debe ser el último carácter de la línea, lo que indica al preprocesador
que la siguiente línea debe estar concatenada en la línea actual, tratándolas como una sola línea.
Esto se puede utilizar varias veces seguidas.
// ...
Esto es especialmente útil en macros complejas similares a funciones, que pueden necesitar
cubrir múltiples líneas.
https://riptutorial.com/es/home 627
#define CREATE_OUTPUT_AND_DELETE(Str) \
std::string* tmp = new std::string(Str); \
std::cout << *tmp << std::endl; \
delete tmp;
// ...
En el caso de macros similares a funciones más complejas, puede ser útil darles su propio
alcance para evitar posibles colisiones de nombres o para destruir objetos al final de la macro,
similar a una función real. Un lenguaje común para esto es do mientras 0 , donde la macro está
encerrada en un bloque do-while . Este bloque generalmente no tiene un punto y coma, lo que le
permite tragar un punto y coma.
int x;
DO_STUFF(MyClass, 41153.7, x);
// Compiler sees:
int x;
do {
MyClass temp(some_setup_values);
x = temp.process(41153.7);
} while (0);
También hay macros variadic; de manera similar a las funciones variadic, estas toman un número
variable de argumentos y luego los expanden todos en lugar de un parámetro especial "Varargs",
__VA_ARGS__ .
Tenga en cuenta que durante la expansión, __VA_ARGS__ se puede colocar en cualquier lugar de la
definición y se expandirá correctamente.
VARIADIC2(some_func, 3, 8, 6, 9);
// Compiler sees:
some_func(8, 6, 9, 3);
https://riptutorial.com/es/home 628
silenciosamente la coma sin ninguna sintaxis especial. Otros compiladores, como GCC, requieren
que coloque ## inmediatamente antes de __VA_ARGS__ . Debido a esto, es aconsejable definir
condicionalmente macros variables cuando la portabilidad es una preocupación.
// In this example, COMPILER is a user-defined macro specifying the compiler being used.
Los errores de compilación se pueden generar utilizando el preprocesador. Esto es útil por varias
razones, algunas de las cuales incluyen, notificar a un usuario si están en una plataforma no
compatible o en un compilador no compatible.
#ifdef __APPLE__
#error "Apple products are not supported in this release"
#endif
Macros predefinidas
Las macros predefinidas son aquellas que define el compilador (en contraste con las definidas por
el usuario en el archivo fuente). Esas macros no deben ser redefinidas o no definidas por el
usuario.
• __LINE__ contiene el número de línea de la línea en la que se utiliza esta macro, y puede
modificarse mediante la directiva #line .
• __FILE__ contiene el nombre de archivo del archivo en el que se usa esta macro y puede
modificarse mediante la directiva #line .
• __DATE__ contiene la fecha (en formato "Mmm dd yyyy" ) de la compilación del archivo, donde
Mmm se formatea como si se hubiera obtenido mediante una llamada a std::asctime() .
• __TIME__ contiene el tiempo (en formato "hh:mm:ss" ) de la compilación del archivo.
• __cplusplus es definido por compiladores C ++ (conformes) mientras compila archivos C ++.
Su valor es la versión estándar con la que el compilador es totalmente 199711L , es decir,
199711L para C ++ 98 y C ++ 03, 201103L para C ++ 11 y 201402L para C ++ 14 estándar.
c ++ 11
https://riptutorial.com/es/home 629
• __STDC_HOSTED__ se define como 1 si la implementación está alojada , o 0 si es independiente
.
c ++ 17
Además, las siguientes macros pueden ser predefinidas por las implementaciones y pueden estar
o no presentes:
c ++ 11
También vale la pena mencionar __func__ , que no es una macro, sino una función predefinida
variable local. Contiene el nombre de la función en la que se utiliza, como una matriz de
caracteres estáticos en un formato definido por la implementación.
Además de esas macros predefinidas estándar, los compiladores pueden tener su propio conjunto
de macros predefinidas. Uno debe referirse a la documentación del compilador para aprenderlos.
P.ej:
• gcc
• Microsoft Visual C ++
• sonido metálico
• Compilador Intel C ++
Algunas de las macros son solo para consultar el soporte de alguna característica:
https://riptutorial.com/es/home 630
}
#endif
c ++ 11
Macros x
Una técnica idiomática para generar estructuras de código repetidas en tiempo de compilación.
Ejemplo:
#define LIST \
X(dog) \
X(cat) \
X(racoon)
// class Animal {
// public:
// void say();
// };
int main() {
#define X(name) name.say();
LIST
#undef X
https://riptutorial.com/es/home 631
return 0;
}
Animal dog;
Animal cat;
Animal racoon;
int main() {
dog.say();
cat.say();
racoon.say();
return 0;
}
A medida que las listas se vuelven más grandes (digamos, más de 100 elementos), esta técnica
ayuda a evitar el pegado excesivo de copias.
Fuente: https://en.wikipedia.org/wiki/X_Macro
Si definir una X irrelevante para la costura antes de usar LIST no es de su agrado, también puede
pasar un nombre de macro como argumento:
#define LIST(MACRO) \
MACRO(dog) \
MACRO(cat) \
MACRO(racoon)
Ahora, especifica explícitamente qué macro se debe usar al expandir la lista, por ejemplo,
Si cada invocación de la MACRO debe tomar parámetros adicionales - constante con respecto a la
lista, se pueden usar macros variadic
https://riptutorial.com/es/home 632
#define FORWARD_DECLARE(name, type, prefix) type prefix##name;
LIST(FORWARD_DECLARE,Animal,anim_)
LIST(FORWARD_DECLARE,Object,obj_)
se expandirá a
Animal anim_dog;
Animal anim_cat;
Animal anim_racoon;
Object obj_dog;
Object obj_cat;
Object obj_racoon;
La mayoría, pero no todas, las implementaciones de C ++ son compatibles con la directiva #pragma
once , que garantiza que el archivo solo se incluya una vez en una única compilación. No es parte
de ninguna norma ISO C ++. Por ejemplo:
// Foo.h
#pragma once
class Foo
{
};
Si bien #pragma once evita algunos problemas asociados con la inclusión de guardias , un #pragma ,
por definición en los estándares, es inherentemente un gancho específico del compilador y será
ignorado silenciosamente por los compiladores que no lo admiten. Los proyectos que usan
#pragma once deben modificarse para cumplir con los estándares.
#pragma once combinado con incluir guardas, fue el diseño recomendado para los archivos de
encabezado al escribir aplicaciones basadas en MFC en Windows, y fue generado por la add
class Visual Studio, el add dialog add windows asistentes de add windows . Por lo tanto, es muy
común encontrarlos combinados en los solicitantes de Windows en C ++.
Operadores de preprocesador
https://riptutorial.com/es/home 633
PRINT(This line will be converted to string by preprocessor);
// Compiler sees
printf("This line will be converted to string by preprocessor""\n");
El compilador concatena dos cadenas y el argumento final printf() será una cadena literal con un
carácter de nueva línea al final.
El preprocesador ignorará los espacios antes o después del argumento de la macro. Así que
debajo de la declaración impresa nos dará el mismo resultado.
Si el parámetro de la cadena literal requiere una secuencia de escape como antes de una comilla
doble (), el preprocesador lo insertará automáticamente.
variableY = 15
https://riptutorial.com/es/home 634
Capítulo 104: Pruebas unitarias en C ++
Introducción
La prueba de unidad es un nivel en la prueba de software que valida el comportamiento y la
corrección de las unidades de código.
Examples
Prueba de google
Ejemplo mínimo
// main.cpp
#include <gtest/gtest.h>
#include <iostream>
// Google Test test cases are created using a C++ preprocessor macro
// Here, a "test suite" name and a specific "test name" are provided.
TEST(module_name, test_name) {
std::cout << "Hello world!" << std::endl;
// Google Test will also provide macros for assertions.
ASSERT_EQ(1+1, 2);
}
return RUN_ALL_TESTS();
}
Captura
https://riptutorial.com/es/home 635
Catch es una biblioteca de solo encabezado que le permite usar el estilo de prueba de unidad
TDD y BDD .
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 5 );
https://riptutorial.com/es/home 636
Capítulo 105: Punteros
Introducción
Un puntero es una dirección que se refiere a una ubicación en la memoria. Se utilizan
comúnmente para permitir que las funciones o estructuras de datos conozcan y modifiquen la
memoria sin tener que copiar la memoria a la que se hace referencia. Los punteros se pueden
utilizar con tipos primitivos (integrados) o definidos por el usuario.
Los punteros hacen uso de la "desreferencia" * , "dirección de" & , y "flecha" -> operadores. Los
operadores '*' y '->' se usan para acceder a la memoria a la que se apunta, y el operador & se usa
para obtener una dirección en la memoria.
Sintaxis
• <Tipo de datos> * <Nombre de variable>;
• <Tipo de datos> * <Nombre de variable> = & <Nombre de variable del mismo tipo de
datos>;
• <Tipo de datos> * <Nombre de variable> = <Valor del mismo tipo de datos>;
• int * foo; // Un puntero que apunta a un valor entero
• int * bar = & myIntVar;
• barra larga * [2];
• long * bar [] = {& myLongVar1, & myLongVar2}; // Igual a: barra larga * [2]
Observaciones
Tenga en cuenta los problemas al declarar múltiples punteros en la misma línea.
Examples
Fundamentos de puntero
C ++ 11
https://riptutorial.com/es/home 637
Creando una variable de puntero
Se puede crear una variable de puntero utilizando la sintaxis específica * , por ejemplo, int
*pointer_to_int; .
Cuando una variable es de tipo puntero ( int * ), solo contiene una dirección de memoria. La
dirección de la memoria es la ubicación en la que se almacenan los datos del tipo subyacente (
int ).
La diferencia es clara cuando se compara el tamaño de una variable con el tamaño de un puntero
al mismo tipo:
/* Produces:
sizeof(bar) = 24
sizeof(p_bar0) = 8
*/
Puede obtener la dirección de memoria de una variable de un tipo dado prefijando la variable con
la dirección del operador & . El valor devuelto por & es un puntero al tipo subyacente que contiene
la dirección de memoria de la variable (que son datos válidos siempre que la variable no quede
fuera del alcance ).
https://riptutorial.com/es/home 638
big_struct *p_bar1 = p_bar0;
p_bar0 = p_bar2;
p_bar2 = nullptr;
// p_bar0 == &bar
// p_bar1 == nullptr
// p_bar2 == nullptr
(*p_bar0).foo1 = 5;
// Prints 5 as well
std::cout << "baz.foo1 = " << baz.foo1 << std::endl;
https://riptutorial.com/es/home 639
Al desreferenciar un puntero, debe asegurarse de que apunta a datos válidos. La anulación de la
referencia de un puntero no válido (o un puntero nulo) puede provocar una violación de acceso a
la memoria, o leer o escribir datos de basura.
big_struct *never_do_this() {
// This is a local variable. Outside `never_do_this` it doesn't exist.
big_struct retval;
retval.foo1 = 11;
// This returns the address of `retval`.
return &retval;
// `retval` is destroyed and any code using the value returned
// by `never_do_this` has a pointer to a memory location that
// contains garbage data (or is inaccessible).
}
(Clang) warning: address of stack memory associated with local variable 'retval' returned [-
Wreturn-stack-address]
(Gcc) warning: address of local variable ‘retval’ returned [-Wreturn-local-addr]
Por lo tanto, se debe tener cuidado cuando los punteros son argumentos de funciones, ya que
podrían ser nulos:
// Segmentation fault.
naive_code(nullptr);
Operaciones de puntero
Hay dos operadores para punteros: Dirección de operador (&): devuelve la dirección de memoria
de su operando. Operador de contenido de (desreferencia) (*): devuelve el valor de la variable
ubicada en la dirección especificada por su operador.
El asterisco (*) se usa para declarar un puntero con el único propósito de indicar que es un
puntero. No confunda esto con el operador de desreferencia , que se utiliza para obtener el valor
https://riptutorial.com/es/home 640
ubicado en la dirección especificada. Son simplemente dos cosas diferentes representadas con el
mismo signo.
Aritmética de puntero
Incremento / Decremento
Un puntero puede ser incrementado o decrementado (prefijo y postfix). Incrementar un puntero
avanza el valor del puntero al elemento en la matriz, un elemento más allá del elemento apuntado
actualmente. Disminuir un puntero lo mueve al elemento anterior en la matriz.
La aritmética de punteros no está permitida si el tipo al que apunta el puntero no está completo.
void es siempre un tipo incompleto.
Si se incrementa un puntero al elemento final, entonces el puntero apunta a un elemento más allá
del final de la matriz. Tal puntero no puede ser referenciado, pero puede ser disminuido.
Incrementar un puntero al elemento uno más allá del final en la matriz, o disminuir un puntero al
primer elemento en una matriz produce un comportamiento indefinido.
Suma resta
Se pueden agregar valores enteros a los punteros; actúan como incrementos, pero por un número
específico en lugar de por 1. Los valores enteros también se pueden sustraer de los punteros,
actuando como disminución del puntero. Al igual que con el incremento / decremento, el puntero
debe apuntar a un tipo completo.
https://riptutorial.com/es/home 641
Diferencia de puntero
La diferencia entre dos punteros del mismo tipo se puede calcular. Los dos punteros deben estar
dentro del mismo objeto de matriz; De lo contrario, el comportamiento no definido resulta.
https://riptutorial.com/es/home 642
Capítulo 106: Punteros a los miembros
Sintaxis
• Suponiendo una clase llamada Clase ...
• Para los punteros a miembros de clase no estáticos, dadas las siguientes dos definiciones:
○ Instancia de clase;
○ Clase * p = & instancia;
Examples
Punteros a funciones miembro estáticas
Una función miembro static es como una función C / C ++ ordinaria, excepto con alcance:
• Está dentro de una class , por lo que necesita su nombre decorado con el nombre de la
clase;
• Tiene accesibilidad, con public , protected o private .
Por lo tanto, si tiene acceso a la función miembro static y la decora correctamente, puede señalar
la función como cualquier función normal fuera de una class :
typedef int Fn(int); // Fn is a type-of function that accepts an int and returns an int
class Class {
public:
// Note that Static() is of type 'Fn'
static int Static(int i) { return 3*i; }
https://riptutorial.com/es/home 643
}; // Class
int main() {
Fn *fn; // fn is a pointer to a type-of Fn
Para acceder a una función miembro de una clase, debe tener un "identificador" para la instancia
en particular, ya sea como la instancia en sí, o un puntero o una referencia a ella. Dada una
instancia de clase, puedes apuntar a varios de sus miembros con un puntero a miembro, ¡SI
obtienes la sintaxis correcta! Por supuesto, el puntero debe ser declarado del mismo tipo que lo
que está apuntando a ...
typedef int Fn(int); // Fn is a type-of function that accepts an int and returns an int
class Class {
public:
// Note that A() is of type 'Fn'
int A(int a) { return 2*a; }
// Note that B() is of type 'Fn'
int B(int b) { return 3*b; }
}; // Class
int main() {
Class c; // Need a Class instance to play with
Class *p = &c; // Need a Class pointer to play with
A diferencia de los punteros a las variables miembro (en el ejemplo anterior), la asociación entre
la instancia de la clase y el puntero del miembro debe vincularse estrechamente entre paréntesis,
lo que parece un poco extraño (como si .* Y ->* no fueran extraños ¡suficiente!)
Para acceder a un miembro de una class , debe tener un "identificador" para la instancia en
particular, ya sea como la instancia en sí, o como un puntero o una referencia a ella. Dada una
instancia de class , puedes apuntar a varios de sus miembros con un puntero a miembro, ¡SI
obtienes la sintaxis correcta! Por supuesto, el puntero debe ser declarado del mismo tipo que lo
que está apuntando a ...
https://riptutorial.com/es/home 644
class Class {
public:
int x, y, z;
char m, n, o;
}; // Class
int main() {
Class c; // Need a Class instance to play with
Class *p = &c; // Need a Class pointer to play with
• Para definir el tipo de puntero, debe mencionar el tipo base, así como el hecho de que está
dentro de una clase: int Class::*ptr; .
• Si usted tiene una clase o de referencia y desea utilizarlo con un puntero a miembro-, es
necesario utilizar el .* Operador (afín al . Operador).
• Si tiene un puntero a una clase y desea usarlo con un puntero a miembro, debe usar el
operador ->* (similar al operador -> ).
Una variable miembro static es como una variable C / C ++ ordinaria, excepto con alcance:
• Está dentro de una class , por lo que necesita su nombre decorado con el nombre de la
clase;
• Tiene accesibilidad, con public , protected o private .
Por lo tanto, si tiene acceso a la variable miembro static y la decora correctamente, entonces
puede señalar la variable como cualquier variable normal fuera de una class :
class Class {
public:
static int i;
}; // Class
https://riptutorial.com/es/home 645
int j = 2; // Just another global variable
int main() {
int k = 3; // Local variable
int *p;
p = &k; // Point to k
*p = 2; // Modify it
p = &j; // Point to j
*p = 3; // Modify it
p = &Class::i; // Point to Class::i
*p = 4; // Modify it
} // main()
https://riptutorial.com/es/home 646
Capítulo 107: Punteros inteligentes
Sintaxis
• std::shared_ptr<ClassType> variableName = std::make_shared<ClassType>(arg1, arg2, ...);
• std::shared_ptr<ClassType> variableName (new ClassType(arg1, arg2, ...));
• std::unique_ptr<ClassType> variableName = std::make_unique<ClassType>(arg1, arg2, ...); // C
++ 14
• std::unique_ptr<ClassType> variableName (new ClassType(arg1, arg2, ...));
Observaciones
C ++ no es un lenguaje gestionado por la memoria. La memoria asignada dinámicamente (es
decir, los objetos creados con new ) se "filtrará" si no se desasigna explícitamente (con delete ). Es
responsabilidad del programador asegurarse de que la memoria asignada dinámicamente se
libere antes de descartar el último puntero a ese objeto.
Los punteros inteligentes se prefieren a los punteros "sin procesar" en la mayoría de los casos.
Hacen explícita la semántica de propiedad de la memoria asignada dinámicamente, comunicando
en sus nombres si se pretende que un objeto sea compartido o de propiedad única.
Examples
Compartir propiedad (std :: shared_ptr)
Para crear varios punteros inteligentes que compartan el mismo objeto, necesitamos crear otro
shared_ptr que shared_ptr alias al primer puntero compartido. Aquí hay 2 formas de hacerlo:
https://riptutorial.com/es/home 647
std::shared_ptr<Foo> secondShared(firstShared); // 1st way: Copy constructing
std::shared_ptr<Foo> secondShared;
secondShared = firstShared; // 2nd way: Assigning
Cualquiera de las formas anteriores convierte a secondShared un puntero compartido que comparte
la propiedad de nuestra instancia de Foo con firstShared .
El puntero inteligente funciona como un puntero en bruto. Esto significa que puedes usar * para
desreferenciarlos. El operador regular -> funciona:
Finalmente, cuando el último alias shared_ptr sale del ámbito, se llama al destructor de nuestra
instancia de Foo .
C ++ 11 C ++ 17
Por ejemplo, para asignar una matriz de 10 enteros, podemos escribir el código como
template<class Arr>
struct shared_array_maker {};
template<class T, std::size_t N>
struct shared_array_maker<T[N]> {
std::shared_ptr<T> operator()const{
auto r = std::make_shared<std::array<T,N>>();
if (!r) return {};
return {r.data(), r};
}
https://riptutorial.com/es/home 648
};
template<class Arr>
auto make_shared_array()
-> decltype( shared_array_maker<Arr>{}() )
{ return shared_array_maker<Arr>{}(); }
C ++ 17
Con C ++ 17, shared_ptr obtuvo soporte especial para tipos de arreglos. Ya no es necesario
especificar el eliminador de matriz de forma explícita, y el puntero compartido se puede anular
mediante el operador de índice de matriz [] :
Los punteros compartidos pueden apuntar a un subobjeto del objeto que posee:
Tanto p2 como p1 poseen el objeto de tipo Foo , pero p2 apunta a su miembro int x . Esto significa
que si p1 queda fuera del alcance o se reasigna, el objeto Foo subyacente seguirá vivo,
asegurándose de que p2 no cuelgue.
Importante: un shared_ptr solo se conoce a sí mismo y a todos los otros shared_ptr que se
crearon con el constructor de alias. No conoce ningún otro puntero, incluidos todos los otros
shared_ptr s creados con una referencia a la misma instancia de Foo :
shared_ptr<int> up = make_shared<int>();
// Transferring the ownership
shared_ptr<int> up2 = move(up);
https://riptutorial.com/es/home 649
// At this point, the reference count of up = 0 and the
// ownership of the pointer is solely with up2 with reference count = 1
Las instancias de std::weak_ptr pueden apuntar a objetos que son propiedad de instancias de
std::shared_ptr y solo se convierten en propietarios temporales. Esto significa que los punteros
débiles no alteran el recuento de referencias del objeto y, por lo tanto, no impiden la eliminación
de un objeto si todos los punteros compartidos del objeto se reasignan o destruyen.
#include <memory>
#include <vector>
struct TreeNode {
std::weak_ptr<TreeNode> parent;
std::vector< std::shared_ptr<TreeNode> > children;
};
int main() {
// Create a TreeNode to serve as the root/parent.
std::shared_ptr<TreeNode> root(new TreeNode);
// Reset the root shared pointer, destroying the root object, and
// subsequently its child nodes.
root.reset();
}
A medida que los nodos secundarios se agregan a los secundarios del nodo raíz, su parent
std::weak_ptr miembro se establece en el nodo raíz. El parent miembro se declara como un
puntero débil en lugar de un puntero compartido, de manera que el recuento de referencia del
nodo raíz no se incrementa. Cuando el nodo raíz se reinicia al final de main() , la raíz se destruye.
Dado que el único resto de std::shared_ptr referencias a los nodos secundarios estaban
contenidos en la colección de la raíz children , todos los nodos hijos son posteriormente
destruidos también.
Debido a los detalles de la implementación del bloque de control, la memoria asignada shared_ptr
puede no liberarse hasta que el shared_ptr referencia weak_ptr y el weak_ptr referencia weak_ptr
lleguen a cero.
#include <memory>
int main()
https://riptutorial.com/es/home 650
{
{
std::weak_ptr<int> wk;
{
// std::make_shared is optimized by allocating only once
// while std::shared_ptr<int>(new int(42)) allocates twice.
// Drawback of std::make_shared is that control block is tied to our integer
std::shared_ptr<int> sh = std::make_shared<int>(42);
wk = sh;
// sh memory should be released at this point...
}
// ... but wk is still alive and needs access to control block
}
// now memory is released (sh and wk)
}
Dado que std::weak_ptr no mantiene vivo su objeto referenciado, no es posible el acceso directo
de datos a través de std::weak_ptr . En su lugar, proporciona una función miembro lock() que
intenta recuperar un std::shared_ptr para el objeto al que se hace referencia:
#include <cassert>
#include <memory>
int main()
{
{
std::weak_ptr<int> wk;
std::shared_ptr<int> sp;
{
std::shared_ptr<int> sh = std::make_shared<int>(42);
wk = sh;
// calling lock will create a shared_ptr to the object referenced by wk
sp = wk.lock();
// sh will be destroyed after this point, but sp is still alive
}
// sp still keeps the data alive.
// At this point we could even call lock() again
// to retrieve another shared_ptr to the same data from wk
assert(*sp == 42);
assert(!wk.expired());
// resetting sp will delete the data,
// as it is currently the last shared_ptr with ownership
sp.reset();
// attempting to lock wk now will return an empty shared_ptr,
// as the data has already been deleted
sp = wk.lock();
assert(!sp);
assert(wk.expired());
}
}
C ++ 11
A std::unique_ptr es una plantilla de clase que administra la vida útil de un objeto almacenado
dinámicamente. A diferencia de std::shared_ptr , el objeto dinámico es propiedad de solo una
https://riptutorial.com/es/home 651
instancia de std::unique_ptr en cualquier momento,
Sólo la variable ptr mantiene un puntero a un int asignado dinámicamente. Cuando un puntero
único que posee un objeto queda fuera del alcance, el objeto propio se elimina, es decir, se llama
a su destructor si el objeto es de clase y se libera la memoria para ese objeto.
Para usar std::unique_ptr y std::make_unique con tipos de matriz, use sus especializaciones de
matriz:
Puede transferir la propiedad del contenido de un puntero inteligente a otro puntero utilizando
std::move , lo que hará que el puntero inteligente original apunte a nullptr .
// 1. std::unique_ptr
std::unique_ptr<int> ptr = std::make_unique<int>();
// Change value to 1
*ptr = 1;
// 2. std::unique_ptr (by moving 'ptr' to 'ptr2', 'ptr' doesn't own the object anymore)
std::unique_ptr<int> ptr2 = std::move(ptr);
https://riptutorial.com/es/home 652
funciones de fábrica, ya que transmite claramente la semántica de propiedad de la devolución: la
persona que llama posee el unique_ptr resultante y es responsable de ello.
std::unique_ptr<int> foo()
{
std::unique_ptr<int> ptr = std::make_unique<int>(59);
return ptr;
}
int* foo_cpp03();
C ++ 14
C ++ 11
A diferencia del puntero inteligente tonto ( std::auto_ptr ), unique_ptr también se puede crear una
instancia con asignación de vectores ( no std::vector ). Los ejemplos anteriores fueron para
asignaciones escalares . Por ejemplo, para tener una matriz de enteros asignada dinámicamente
para 10 elementos, debe especificar int[] como tipo de plantilla (y no solo int ):
No tiene que preocuparse por la desasignación. Esta plantilla especializada de la versión llama a
https://riptutorial.com/es/home 653
los constructores y destructores adecuadamente. El uso de la versión vectorizada de unique_ptr o
un vector sí mismo es una opción personal.
Muchas interfaces C, como SDL2, tienen sus propias funciones de eliminación. Esto significa que
no puede utilizar punteros inteligentes directamente:
En su lugar, necesita definir su propio deleter. Los ejemplos aquí utilizan la estructura SDL_Surface
que debería liberarse utilizando la función SDL_FreeSurface() , pero deberían ser adaptables a
muchas otras interfaces en C.
El eliminador debe ser invocable con un argumento de puntero, y por lo tanto puede ser, por
ejemplo, un puntero de función simple:
Cualquier otro objeto llamable también funcionará, por ejemplo, una clase con un operator() :
struct SurfaceDeleter {
void operator()(SDL_Surface* surf) {
SDL_FreeSurface(surf);
}
};
Esto no solo le proporciona una administración de memoria automática segura y sin sobrecarga
(si utiliza unique_ptr ), unique_ptr que también obtiene una seguridad excepcional.
Tenga en cuenta que el borrado es parte del tipo para unique_ptr , y la implementación puede
usar la optimización de la base vacía para evitar cualquier cambio en el tamaño de los borradores
personalizados vacíos. Entonces, mientras std::unique_ptr<SDL_Surface, SurfaceDeleter> y
std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> resuelven el mismo problema de una manera
similar, el primer tipo sigue siendo solo del tamaño de un puntero mientras que este último tipo
debe contener dos punteros: tanto la SDL_Surface* como la función de puntero! Cuando se tienen
eliminaciones personalizadas de función libre, es preferible envolver la función en un tipo vacío.
En los casos en que el conteo de referencias es importante, se podría usar un shared_ptr lugar de
https://riptutorial.com/es/home 654
un unique_ptr . shared_ptr siempre almacena un eliminador, esto borra el tipo del eliminador, lo
que podría ser útil en las API. Las desventajas de usar shared_ptr sobre unique_ptr incluyen un
mayor costo de memoria para almacenar el eliminador y un costo de rendimiento para mantener
el recuento de referencia.
C ++ 17
Con la template auto , podemos hacer que sea aún más fácil envolver nuestros borrados
personalizados:
Aquí, el propósito de auto es manejar todas las funciones libres, ya sea que devuelvan void (por
ejemplo, SDL_FreeSurface ) o no (por ejemplo, fclose ).
C ++ 11
Al igual que con todos los punteros inteligentes, std::auto_ptr limpia automáticamente los
recursos (ver RAII ):
{
std::auto_ptr<int> p(new int(42));
https://riptutorial.com/es/home 655
std::cout << *p;
} // p is deleted here, no memory leaked
std::auto_ptr<X> px = ...;
std::auto_ptr<X> py = px;
// px is now empty
Esto permite usar std :: auto_ptr para mantener la propiedad explícita y única ante el peligro de
perder la propiedad sin querer:
void f(std::auto_ptr<X> ) {
// assumes ownership of X
// deletes it at end of scope
};
std::auto_ptr<X> px = ...;
f(px); // f acquires ownership of underlying X
// px is now empty
px->foo(); // NPE!
// px.~auto_ptr() does NOT delete
T* release() {
T* tmp = ptr;
ptr = nullptr;
return tmp;
}
https://riptutorial.com/es/home 656
Esto rompe la semántica de la copia, que requiere que copiar un objeto te deje con dos versiones
equivalentes. Para cualquier tipo de copia, T , debería poder escribir:
T a = ...;
T b(a);
assert(b == a);
Pero para auto_ptr , este no es el caso. Como resultado, no es seguro poner auto_ptr s en
contenedores.
Tenga en cuenta que el objeto debe crearse como shared_ptr en primer lugar:
#include <memory>
class A: public enable_shared_from_this<A> {
};
A* ap1 =new A();
shared_ptr<A> ap2(ap1); // First prepare a shared pointer to the object and hold it!
// Then get a shared pointer to the object from the object itself
shared_ptr<A> ap3 = ap1->shared_from_this();
int c3 =ap3.use_count(); // =2: pointing to the same object
int main()
{
...
auto w = std::make_shared< Widget >();
w -> DoSomething();
...
}
https://riptutorial.com/es/home 657
std::bad_alloc en std::bad_alloc lugar.
Un value_ptr es un puntero inteligente que se comporta como un valor. Cuando se copia, copia su
contenido. Cuando se crea, crea su contenido.
// Like std::default_delete:
template<class T>
struct default_copier {
// a copier must handle a null T const* in and return null:
T* operator()(T const* tin)const {
if (!tin) return nullptr;
return new T(*tin);
}
void operator()(void* dest, T const* tin)const {
if (!tin) return;
return new(dest) T(*tin);
}
};
// tag class to handle empty case:
struct empty_ptr_t {};
constexpr empty_ptr_t empty_ptr{};
// the value pointer type itself:
template<class T, class Copier=default_copier<T>, class Deleter=std::default_delete<T>,
class Base=std::unique_ptr<T, Deleter>
https://riptutorial.com/es/home 658
>
struct value_ptr:Base, private Copier {
using copier_type=Copier;
// also typedefs from unique_ptr
using Base::Base;
value_ptr( T const& t ):
Base( std::make_unique<T>(t) ),
Copier()
{}
value_ptr( T && t ):
Base( std::make_unique<T>(std::move(t)) ),
Copier()
{}
// almost-never-empty:
value_ptr():
Base( std::make_unique<T>() ),
Copier()
{}
value_ptr( empty_ptr_t ) {}
https://riptutorial.com/es/home 659
}
value_ptr& operator=( T && t ) {
if (*this) {
**this = std::move(t);
} else {
*this = value_ptr(std::move(t));
}
return *this;
}
T& get() { return **this; }
T const& get() const { return **this; }
T* get_pointer() {
if (!*this) return nullptr;
return std::addressof(get());
}
T const* get_pointer() const {
if (!*this) return nullptr;
return std::addressof(get());
}
// operator-> from unique_ptr
};
template<class T, class...Args>
value_ptr<T> make_value_ptr( Args&&... args ) {
return {std::make_unique<T>(std::forward<Args>(args)...)};
}
Este valor_ptr en particular solo está vacío si lo construyes con empty_ptr_t o si te mueves de él.
Expone el hecho de que es un unique_ptr , por explicit operator bool() const trabaja en él. .get()
se ha cambiado para devolver una referencia (ya que casi nunca está vacía), y .get_pointer()
devuelve un puntero.
Este puntero inteligente puede ser útil para los casos pImpl , donde queremos valores semánticos,
pero tampoco queremos exponer los contenidos de pImpl fuera del archivo de implementación.
Con una Copier no predeterminada, incluso puede manejar clases de base virtual que saben
cómo producir instancias de sus derivadas y convertirlas en tipos de valor.
https://riptutorial.com/es/home 660
Capítulo 108: RAII: la adquisición de recursos
es la inicialización
Observaciones
RAII significa R isource A cquisition I s I nitialization. RAII es una expresión idiomática utilizada
para vincular los recursos con la vida útil de los objetos. También se conoce como SBRM
(Administración de recursos basada en el alcance) o RRID (La publicación del recurso es
destrucción). En C ++, el destructor para un objeto siempre se ejecuta cuando un objeto se sale
del alcance, podemos aprovechar eso para vincular la limpieza de recursos con la destrucción de
objetos.
Cada vez que necesite adquirir algún recurso (por ejemplo, un bloqueo, un identificador de
archivo, un búfer asignado) que eventualmente necesitará liberar, debe considerar usar un objeto
para manejar esa administración de recursos por usted. El desenrollado de la pila ocurrirá
independientemente de la excepción o la salida temprana del alcance, por lo que el objeto del
controlador de recursos limpiará el recurso por usted sin que tenga que considerar
cuidadosamente todas las rutas de código actuales y futuras posibles.
Vale la pena señalar que RAII no libera completamente al desarrollador de pensar en la vida útil
de los recursos. Un caso es, obviamente, una llamada crash o exit (), que evitará que se llame a
los destructores. Como el sistema operativo limpiará los recursos locales del proceso, como la
memoria, después de que finalice el proceso, esto no es un problema en la mayoría de los casos.
Sin embargo, con los recursos del sistema (es decir, canalizaciones con nombre, archivos de
bloqueo, memoria compartida), todavía se necesitan recursos para tratar el caso en el que un
proceso no se limpió después de sí mismo, es decir, en la prueba de inicio si el archivo de
bloqueo está ahí, si es así, Verifique que el proceso con el pid realmente existe, luego actúe en
consecuencia.
Otra situación es cuando un proceso de Unix llama a una función de la familia exec, es decir,
después de un fork-exec para crear un nuevo proceso. Aquí, el proceso hijo tendrá una copia
completa de la memoria de los padres (incluidos los objetos RAII), pero una vez que se llamó a
exec, no se llamará a ninguno de los destructores en ese proceso. Por otro lado, si un proceso se
bifurca y ninguno de los procesos llama exec, todos los recursos se limpian en ambos procesos.
Esto es correcto solo para todos los recursos que realmente se duplicaron en la bifurcación, pero
con los recursos del sistema, ambos procesos solo tendrán una referencia al recurso (es decir, la
ruta de acceso a un archivo de bloqueo) y ambos intentarán liberarlo individualmente, lo que
podría causar El otro proceso para fallar.
Examples
Cierre
Bloqueo incorrecto:
https://riptutorial.com/es/home 661
std::mutex mtx;
void bad_lock_example() {
mtx.lock();
try
{
foo();
bar();
if (baz()) {
mtx.unlock(); // Have to unlock on each exit point.
return;
}
quux();
mtx.unlock(); // Normal unlock happens here.
}
catch(...) {
mtx.unlock(); // Must also force unlock in the presence of
throw; // exceptions and allow the exception to continue.
}
}
Esa es la forma incorrecta de implementar el bloqueo y desbloqueo del mutex. Para garantizar la
correcta liberación del mutex con unlock() , el programador debe asegurarse de que todos los
flujos que resultan en la salida de la función resulten en una llamada a unlock() . Como se
muestra arriba, este es un proceso frágil ya que requiere que los mantenedores continúen
siguiendo el patrón manualmente.
El uso de una clase diseñada apropiadamente para implementar RAII, el problema es trivial:
std::mutex mtx;
void good_lock_example() {
std::lock_guard<std::mutex> lk(mtx); // constructor locks.
// destructor unlocks. destructor call
// guaranteed by language.
foo();
bar();
if (baz()) {
return;
}
quux();
}
lock_guard es una plantilla de clase extremadamente simple que simplemente llama a lock() en su
argumento en su constructor, mantiene una referencia al argumento y llama a unlock() en el
argumento en su destructor. Es decir, cuando el lock_guard queda fuera del alcance, se garantiza
que el mutex está desbloqueado. No importa si la razón por la que se salió del alcance es una
excepción o una devolución anticipada: todos los casos se manejan; independientemente del flujo
de control, garantizamos que desbloquearemos correctamente.
Finalmente / ScopeExit
Para los casos en que no queremos escribir clases especiales para manejar algún recurso,
podemos escribir una clase genérica:
https://riptutorial.com/es/home 662
template<typename Function>
class Finally final
{
public:
explicit Finally(Function f) : f(std::move(f)) {}
~Finally() { f(); } // (1) See below
Y su uso de ejemplo.
v[i] += 42;
auto autoRollBackChange = onExit([&](){ v[i] -= 42; });
Nota (1): Debe considerarse alguna discusión sobre la definición del destructor para manejar la
excepción:
ScopeSuccess (c ++ 17)
C ++ 17
#include <exception>
#include <iostream>
https://riptutorial.com/es/home 663
{
private:
F f;
int uncaughtExceptionCount = std::uncaught_exceptions();
public:
explicit ScopeSuccess(const F& f) : f(f) {}
ScopeSuccess(const ScopeSuccess&) = delete;
ScopeSuccess& operator =(const ScopeSuccess&) = delete;
struct Foo {
~Foo() {
try {
ScopeSuccess logSuccess{[](){std::cout << "Success 1\n";}};
// Scope succeeds,
// even if Foo is destroyed during stack unwinding
// (so when 0 < std::uncaught_exceptions())
// (or previously std::uncaught_exception() == true)
} catch (...) {
}
try {
ScopeSuccess logSuccess{[](){std::cout << "Success 2\n";}};
};
int main()
{
try {
Foo foo;
Salida:
Success 1
ScopeFail (c ++ 17)
C ++ 17
Gracias a int std::uncaught_exceptions() , podemos implementar una acción que se ejecuta solo
en caso de fallo (se produce una excepción en el alcance). Anteriormente, bool
https://riptutorial.com/es/home 664
std::uncaught_exception() solo permite detectar si se está ejecutando algún desenrollado de pila.
#include <exception>
#include <iostream>
struct Foo {
~Foo() {
try {
ScopeFail logFailure{[](){std::cout << "Fail 1\n";}};
// Scope succeeds,
// even if Foo is destroyed during stack unwinding
// (so when 0 < std::uncaught_exceptions())
// (or previously std::uncaught_exception() == true)
} catch (...) {
}
try {
ScopeFail logFailure{[](){std::cout << "Failure 2\n";}};
};
int main()
{
try {
Foo foo;
Salida:
Failure 2
https://riptutorial.com/es/home 665
Lea RAII: la adquisición de recursos es la inicialización en línea:
https://riptutorial.com/es/cplusplus/topic/1320/raii--la-adquisicion-de-recursos-es-la-inicializacion
https://riptutorial.com/es/home 666
Capítulo 109: Recursion en C ++
Examples
Uso de la recursión de la cola y la recursión del estilo de Fibonnaci para
resolver la secuencia de Fibonnaci
La forma más simple y más obvia de usar la recursión para obtener el enésimo término de la
secuencia de Fibonnaci es esta
int get_term_fib(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
return get_term_fib(n - 1) + get_term_fib(n - 2);
}
Sin embargo, este algoritmo no se escala para términos más altos: para n más grande y más
grande, la cantidad de llamadas a funciones que necesita realizar crece exponencialmente. Esto
puede ser reemplazado con una simple recursión de cola.
Las funciones recursivas pueden ser bastante caras. Si son funciones puras (funciones que
siempre devuelven el mismo valor cuando se las llama con los mismos argumentos, y que no
dependen ni modifican el estado externo), pueden hacerse considerablemente más rápido a
expensas de la memoria almacenando los valores ya calculados.
#include <map>
int fibonacci(int n)
{
static std::map<int, int> values;
https://riptutorial.com/es/home 667
if (n==0 || n==1)
return n;
std::map<int,int>::iterator iter = values.find(n);
if (iter == values.end())
{
return values[n] = fibonacci(n-1) + fibonacci(n-2);
}
else
{
return iter->second;
}
}
Tenga en cuenta que a pesar de usar la fórmula de recursión simple, en la primera llamada esta
función es $ O (n) $. En llamadas subsiguientes con el mismo valor, es por supuesto $ O (1) $.
Tenga en cuenta, sin embargo, que esta implementación no es reentrante. Además, no permite
deshacerse de los valores almacenados. Una implementación alternativa sería permitir que el
mapa se pase como argumento adicional:
#include <map>
Para esta versión, se requiere que la persona que llama mantenga el mapa con los valores
almacenados. Esto tiene la ventaja de que la función ahora está reentrada y que la persona que
llama puede eliminar valores que ya no son necesarios, lo que ahorra memoria. Tiene la
desventaja de que rompe la encapsulación; la persona que llama puede cambiar la salida al
rellenar el mapa con valores incorrectos.
https://riptutorial.com/es/home 668
Capítulo 110: Reenvío perfecto
Observaciones
El reenvío perfecto requiere reenviar las referencias para preservar los ref-calificadores de los
argumentos. Tales referencias aparecen solo en un contexto deducido . Es decir:
template<class T>
void f(T&& x) // x is a forwarding reference, because T is deduced from a call to f()
{
g(std::forward<T>(x)); // g() will receive an lvalue or an rvalue, depending on x
}
template<class T>
struct a
{
a(T&& x); // x is a rvalue reference, not a forwarding reference
};
C ++ 17
a example1(1);
// same as a<int> example1(1);
int x = 1;
a example2(x);
// same as a<int&> example2(x);
Examples
Funciones de fábrica
Supongamos que queremos escribir una función de fábrica que acepte una lista arbitraria de
argumentos y pase esos argumentos sin modificar a otra función. Un ejemplo de una función de
este tipo es make_unique , que se utiliza para construir de forma segura una nueva instancia de T y
devolver un unique_ptr<T> que posee la instancia.
Las reglas de lenguaje con respecto a las plantillas variadas y las referencias de valores nos
permiten escribir dicha función.
https://riptutorial.com/es/home 669
return unique_ptr<T>(new T(std::forward<A>(args)...));
}
El uso de puntos suspensivos ... indica un paquete de parámetros, que representa un número
arbitrario de tipos. El compilador expandirá este paquete de parámetros al número correcto de
argumentos en el sitio de la llamada. Estos argumentos luego se pasan al constructor de T
usando std::forward . Esta función es necesaria para conservar los ref-calificadores de los
argumentos.
struct foo
{
foo() {}
foo(const foo&) {} // copy constructor
foo(foo&&) {} // copy constructor
foo(int, int, int) {}
};
foo f;
auto p1 = make_unique<foo>(f); // calls foo::foo(const foo&)
auto p2 = make_unique<foo>(std::move(f)); // calls foo::foo(foo&&)
auto p3 = make_unique<foo>(1, 2, 3);
https://riptutorial.com/es/home 670
Capítulo 111: Referencias
Examples
Definiendo una referencia
Las referencias se comportan de manera similar, pero no del todo como punteros const. Una
referencia se define mediante el sufijo de un signo & a un nombre de tipo.
int i = 10;
int &refi = i;
// Common pitfall :
// int& refi = i, k = j;
// refi will be of type int&.
// though, k will be of type int, not int&!
Tampoco puede enlazar directamente una referencia a nullptr , a diferencia de los punteros:
Una referencia en C ++ es solo un Alias u otro nombre de una variable. Al igual que la mayoría de
nosotros, podemos referirnos usando nuestro nombre de pasaporte y nuestro nombre de usuario.
https://riptutorial.com/es/home 671
int main() {
int i = 10;
int &j = i;
cout<<&i<<endl;
cout<<&b<<endl;
return 0;
}
En el ejemplo anterior, ambos cout imprimirán la misma dirección. La situación será la misma si
tomamos una variable como referencia en una función.
int main() {
int i = 10;
cout<<"Address inside Main => "<<&i<<endl;
func(i);
return 0;
}
Como sabemos ahora que las C++ References son solo un alias, y para que se cree un alias,
necesitamos tener algo a lo que el Alias pueda referirse.
Esa es la razón precisa por la que la declaración como esta arrojará un error de compilación
int &i;
https://riptutorial.com/es/home 672
Capítulo 112: Regla de una definición (ODR)
Examples
Función multiplicada definida
La consecuencia más importante de la regla de definición única es que las funciones no en línea
con enlace externo solo deben definirse una vez en un programa, aunque se pueden declarar
varias veces. Por lo tanto, tales funciones no deben definirse en encabezados, ya que un
encabezado puede incluirse varias veces desde diferentes unidades de traducción.
foo.h :
#ifndef FOO_H
#define FOO_H
#include <iostream>
void foo() { std::cout << "foo"; }
void bar();
#endif
foo.cpp :
#include "foo.h"
void bar() { std:: cout << "bar"; }
main.cpp :
#include "foo.h"
int main() {
foo();
bar();
}
En este programa, la función foo se define en el encabezado foo.h , que se incluye dos veces:
una vez desde foo.cpp y una vez desde main.cpp . Por lo tanto, cada unidad de traducción
contiene su propia definición de foo . Tenga en cuenta que los guardias de foo.h en foo.h no
evitan que esto suceda, ya que tanto foo.cpp como main.cpp incluyen foo.h separado . El resultado
más probable de intentar construir este programa es un error de tiempo de enlace que identifica a
foo como si se hubiera definido de forma múltiple.
Para evitar tales errores, se deben declarar funciones en los encabezados y definirlas en los
archivos .cpp correspondientes, con algunas excepciones (ver otros ejemplos).
Funciones en linea
Una función declarada en inline se puede definir en múltiples unidades de traducción, siempre
que todas las definiciones sean idénticas. También debe definirse en cada unidad de traducción
https://riptutorial.com/es/home 673
en la que se utiliza. Por lo tanto, las funciones en línea deben definirse en los encabezados y no
es necesario mencionarlas en el archivo de implementación.
foo.h :
#ifndef FOO_H
#define FOO_H
#include <iostream>
inline void foo() { std::cout << "foo"; }
void bar();
#endif
foo.cpp :
#include "foo.h"
void bar() {
// more complicated definition
}
main.cpp :
#include "foo.h"
int main() {
foo();
bar();
}
En este ejemplo, la función más simple foo se define en línea en el archivo de encabezado,
mientras que la bar funciones más complicada no está en línea y se define en el archivo de
implementación. Las unidades de traducción foo.cpp y main.cpp contienen definiciones de foo ,
pero este programa está bien formado ya que foo está en línea.
Una función definida dentro de una definición de clase (que puede ser una función miembro o una
función amiga) está implícitamente en línea. Por lo tanto, si una clase se define en un
encabezado, las funciones miembro de la clase se pueden definir dentro de la definición de la
clase, aunque las definiciones se pueden incluir en varias unidades de traducción:
// in foo.h
class Foo {
void bar() { std::cout << "bar"; }
void baz();
};
// in foo.cpp
void Foo::baz() {
// definition
}
La función Foo::baz se define fuera de línea, por lo que no es una función en línea y no debe
definirse en el encabezado.
https://riptutorial.com/es/home 674
Violación ODR a través de la resolución de sobrecarga
Incluso con tokens idénticos para funciones en línea, se puede violar la ODR si la búsqueda de
nombres no se refiere a la misma entidad. consideremos la func en lo siguiente:
• header.h
void overloaded(int);
inline void func() { overloaded('*'); }
• foo.cpp
#include "header.h"
void foo()
{
func(); // `overloaded` refers to `void overloaded(int)`
}
• bar.cpp
void bar()
{
func(); // `overloaded` refers to `void overloaded(char)`
}
Tenemos una violación de ODR ya que overloaded refiere a diferentes entidades dependiendo de
la unidad de traducción.
https://riptutorial.com/es/home 675
Capítulo 113: Resolución de sobrecarga
Observaciones
La resolución de sobrecarga ocurre en varias situaciones diferentes
• Llamadas a funciones sobrecargadas con nombre. Los candidatos son todas las funciones
encontradas por búsqueda de nombre.
• Llamadas a objeto de clase. Los candidatos suelen ser todos los operadores de llamada de
función sobrecargados de la clase.
• Uso de un operador. Los candidatos son las funciones de operador sobrecargadas en el
ámbito del espacio de nombres, las funciones de operador sobrecargadas en el objeto de la
clase izquierda (si existe) y los operadores integrados.
• Resolución de sobrecarga para encontrar la función de operador de conversión correcta o el
constructor a invocar para una inicialización
○ Para la inicialización directa no de lista ( Class c(value) ), los candidatos son
constructores de Class .
○ Para la inicialización de copia no de lista ( Class c = value ) y para encontrar la función
de conversión definida por el usuario para invocar en una secuencia de conversión
definida por el usuario. Los candidatos son los constructores de Class y si la fuente es
un objeto de clase, su operador de conversión funciona.
○ Para la inicialización de una no clase desde un objeto de clase ( Nonclass c =
classObject ). Los candidatos son las funciones del operador de conversión del objeto
inicializador.
○ Para inicializar una referencia con un objeto de clase ( R &r = classObject ), cuando la
clase tiene funciones de operador de conversión que producen valores que pueden
vincularse directamente a r . Los candidatos son tales funciones de operador de
conversión.
○ Para la inicialización de lista de un objeto de clase no agregado ( Class c{1, 2, 3} ),
los candidatos son los constructores de la lista de inicializadores para una primera
pasada a través de la resolución de sobrecarga. Si esto no encuentra un candidato
viable, se realiza un segundo paso a través de la resolución de sobrecarga, con los
constructores de Class como candidatos.
Examples
Coincidencia exacta
Una sobrecarga sin conversiones necesarias para tipos de parámetros o solo conversiones
necesarias entre tipos que aún se consideran coincidencias exactas es preferible a una
sobrecarga que requiere otras conversiones para poder llamar.
https://riptutorial.com/es/home 676
Cuando un argumento se enlaza con una referencia del mismo tipo, se considera que la
coincidencia no requiere una conversión, incluso si la referencia es más calificada como CV.
A los efectos de la resolución de sobrecarga, se considera que el tipo "matriz de T " coincide
exactamente con el tipo "puntero a T ", y se considera que la función tipo T coincide exactamente
con el tipo de indicador de función T* , aunque ambos requieren conversiones
int a[100];
f(a); // calls f(int*); exact match with array-to-pointer conversion
g(a); // ambiguous; both overloads give exact match
El principio general es que las secuencias de conversión estándar son las más baratas, seguidas
de las secuencias de conversión definidas por el usuario, seguidas de las secuencias de
https://riptutorial.com/es/home 677
conversión de puntos suspensivos.
Un caso especial es la secuencia de inicialización de lista, que no constituye una conversión (una
lista de inicializador no es una expresión con un tipo). Su costo se determina definiéndolo como
equivalente a una de las otras tres secuencias de conversión, según el tipo de parámetro y la
forma de la lista de inicializadores.
La resolución de sobrecarga ocurre después de la búsqueda del nombre. Esto significa que no se
seleccionará una función de mejor coincidencia por resolución de sobrecarga si pierde búsqueda
de nombre:
class C {
public:
static void f(double x);
private:
static void f(int x);
};
C::f(42); // Error! Calls private C::f(int) even though public C::f(double) is viable.
De manera similar, la resolución de sobrecarga ocurre sin verificar si la llamada resultante está
bien formada con respecto a lo explicit :
struct X {
explicit X(int );
X(char );
};
void foo(X );
foo({4}); // X(int) is better much, but expression is
// ill-formed because selected constructor is explicit
Debe tener mucho cuidado al proporcionar una sobrecarga de referencia de reenvío, ya que
puede coincidir demasiado bien:
struct A {
A() = default; // #1
https://riptutorial.com/es/home 678
A(A const& ) = default; // #2
La intención aquí era que A es copiable, y que tenemos este otro constructor que podría inicializar
a otro miembro. Sin embargo:
A a; // calls #1
A b(a); // calls #3!
A(A const& ); // #2
A(A& ); // #3, with T = A&
Ambas son coincidencias exactas, pero #3 toma una referencia a un objeto calificado menos cv
que #2 , por lo que tiene la mejor secuencia de conversión estándar y es la mejor función viable.
La solución aquí es restringir siempre estos constructores (por ejemplo, utilizando SFINAE):
template <class T,
class = std::enable_if_t<!std::is_convertible<std::decay_t<T>*, A*>::value>
>
A(T&& );
El rasgo de tipo aquí es excluir de su consideración cualquier A o clase derivada pública y sin
ambigüedad de A , lo que haría que este constructor no se formara correctamente en el ejemplo
descrito anteriormente (y, por lo tanto, eliminado del conjunto de sobrecarga). Como resultado, se
invoca el constructor de copia, que es lo que queríamos.
https://riptutorial.com/es/home 679
// 3 is not viable because the argument lists don't match
// 4 is not viable because we cannot bind a temporary to
// a non-const lvalue reference
3. Elige el mejor candidato viable. Una función viable F1 es una función mejor que otra función
viable F2 si la secuencia de conversión implícita para cada argumento en F1 no es peor que
la secuencia de conversión implícita correspondiente en F2 , y ...:
3.1. Para algunos argumentos, la secuencia de conversión implícita para ese argumento en
F1 es una mejor secuencia de conversión que para ese argumento en F2 , o
3.2. En una conversión definida por el usuario, la secuencia de conversión estándar desde
el retorno de F1 al tipo de destino es una mejor secuencia de conversión que la del tipo de
retorno de F2 , o
struct A
{
operator int();
operator double();
} a;
3.3. En una vinculación de referencia directa, F1 tiene el mismo tipo de referencia que F2 , o
struct A
{
operator X&(); // #1
operator X&&(); // #2
};
A a;
X& lx = a; // calls #1
X&& rx = a; // calls #2
https://riptutorial.com/es/home 680
int* p;
f(p); // calls #2, more specialized
struct A {
A(A const& ); // #1
A a;
A b(a); // calls #2!
// #1 is not a template but #2 resolves to
// A(A& ), which is a less cv-qualified reference than #1
// which makes it a better implicit conversion sequence
void f(double ) { }
void f(float ) { }
Convertir un tipo entero en el tipo promovido correspondiente es mejor que convertirlo en otro tipo
entero.
Promover un float para double es mejor que convertirlo en algún otro tipo de punto flotante.
Las conversiones aritméticas distintas de las promociones no son mejores ni peores que las otras.
https://riptutorial.com/es/home 681
void g(long x);
void g(long double x);
g(42); // ambiguous
g(3.14); // ambiguous
Por lo tanto, para garantizar que no haya ambigüedad al llamar a una función f con argumentos
integrales o de punto flotante de cualquier tipo estándar, se necesitan un total de ocho
sobrecargas, por lo que para cada tipo de argumento posible, ya sea una sobrecarga Se
seleccionará exactamente o la sobrecarga única con el tipo de argumento promovido.
struct A { int m; };
struct B : A {};
struct C : B {};
La conversión del tipo de clase derivada al tipo de clase base se prefiere a las conversiones
definidas por el usuario. Esto se aplica cuando se pasa por valor o por referencia, así como
cuando se convierte puntero a derivado en puntero a base.
struct Unrelated {
Unrelated(B b);
};
void f(A a);
void f(Unrelated u);
B b;
f(b); // calls f(A)
Una conversión de puntero de clase derivada a clase base también es mejor que la conversión a
void* .
https://riptutorial.com/es/home 682
produce en el momento de la compilación y nunca se convertirá de forma implícita.
Para los punteros a los miembros, que son contravariantes con respecto a la clase, una regla
similar se aplica en la dirección opuesta: se prefiere la clase derivada menos derivada.
Base b;
f(&b); // f(Base*) is better than f(const Base*)
Derived d;
f(&d); // f(const Derived*) is better than f(Base*) though;
// constness is only a "tie-breaker" rule
Del mismo modo, pasar un argumento a un parámetro T& , si es posible, es mejor que pasarlo a
un parámetro const T& , incluso si ambos tienen rango de coincidencia exacta.
Esta regla se aplica también a las funciones miembro calificadas por const, donde es importante
para permitir el acceso mutable a objetos no constantes y el acceso inmutable a objetos const.
class IntVector {
public:
// ...
int* data() { return m_data; }
https://riptutorial.com/es/home 683
const int* data() const { return m_data; }
private:
// ...
int* m_data;
};
IntVector v1;
int* data1 = v1.data(); // Vector::data() is better than Vector::data() const;
// data1 can be used to modify the vector's data
const IntVector v2;
const int* data2 = v2.data(); // only Vector::data() const is viable;
// data2 can't be used to modify the vector's data
De la misma manera, una sobrecarga volátil será menos preferida que una sobrecarga no volátil.
class AtomicInt {
public:
// ...
int load();
int load() volatile;
private:
// ...
};
AtomicInt a1;
a1.load(); // non-volatile overload preferred; no side effect
volatile AtomicInt a2;
a2.load(); // only volatile overload is viable; side effect
static_cast<volatile AtomicInt&>(a1).load(); // force volatile semantics for a1
https://riptutorial.com/es/home 684
Capítulo 114: RTTI: Información de tipo de
tiempo de ejecución
Examples
Nombre de un tipo
#include <iostream>
#include <typeinfo>
int main()
{
int speed = 110;
int
dynamic_cast
Utilice dynamic_cast<>() como una función, que le ayuda a reducir a través de una jerarquía de
herencia ( descripción principal ).
Si debe realizar algún trabajo no polimórfico en algunas clases derivadas B y C , pero recibió la
class A base class A , escriba de esta forma:
class B: public A
{ public: void work4B(){} };
class C: public A
{ public: void work4C(){} };
https://riptutorial.com/es/home 685
La palabra clave typeid es un operador unario que proporciona información de tipo de tiempo de
ejecución sobre su operando si el tipo de operando es un tipo de clase polimórfica. Devuelve un
lvalue de tipo const std::type_info . Se ignora la calificación cv de nivel superior.
struct Base {
virtual ~Base() = default;
};
struct Derived : Base {};
Base* b = new Derived;
assert(typeid(*b) == typeid(Derived{})); // OK
typeid también se puede aplicar a un tipo directamente. En este caso, las primeras referencias de
nivel superior se eliminan, luego se ignora la calificación cv de nivel superior. Por lo tanto, el
ejemplo anterior podría haberse escrito con typeid(Derived) lugar de typeid(Derived{}) :
assert(typeid(*b) == typeid(Derived{})); // OK
Si se aplica typeid a cualquier expresión que no sea del tipo de clase polimórfica, el operando no
se evalúa, y la información de tipo devuelta es para el tipo estático.
struct Base {
// note: no virtual destructor
};
struct Derived : Base {};
Derived d;
Base& b = d;
assert(typeid(b) == typeid(Base)); // not Derived
assert(typeid(std::declval<Base>()) == typeid(Base)); // OK because unevaluated
Utilice dynamic_cast para convertir punteros / referencias dentro de una jerarquía de herencia.
Utilice reinterpret_cast para la reinterpretación de bajo nivel de patrones de bits. Utilizar con
extrema precaución.
Use const_cast para desechar const / volatile. Evita esto a menos que estés atascado usando
una API const-incorrect.
https://riptutorial.com/es/home 686
Capítulo 115: Semáforo
Introducción
Los semáforos no están disponibles en C ++ a partir de ahora, pero se pueden implementar
fácilmente con un mutex y una variable de condición.
Examples
Semáforo C ++ 11
#include <mutex>
#include <condition_variable>
class Semaphore {
public:
Semaphore (int count_ = 0)
: count(count_)
{
}
La siguiente función agrega cuatro hilos. Tres hilos compiten por el semáforo, que se establece
en una cuenta de uno. Un subproceso más lento llama a notify_one() , lo que permite que uno de
https://riptutorial.com/es/home 687
los subprocesos en espera continúe.
El resultado es que s1 comienza a girar de inmediato, lo que hace que el count uso del Semáforo
permanezca por debajo de 1. Los otros subprocesos esperan, a su vez, la variable de condición
hasta que se llama a notificar ().
int main()
{
Semaphore sem(1);
thread s1([&]() {
while(true) {
this_thread::sleep_for(std::chrono::seconds(5));
sem.wait( 1 );
}
});
thread s2([&]() {
while(true){
sem.wait( 2 );
}
});
thread s3([&]() {
while(true) {
this_thread::sleep_for(std::chrono::milliseconds(600));
sem.wait( 3 );
}
});
thread s4([&]() {
while(true) {
this_thread::sleep_for(std::chrono::seconds(5));
sem.notify( 4 );
}
});
s1.join();
s2.join();
s3.join();
s4.join();
...
}
https://riptutorial.com/es/home 688
Capítulo 116: Separadores de dígitos
Examples
Separador de dígitos
Los literales numéricos de más de unos pocos dígitos son difíciles de leer.
• Pronuncia 7237498123.
• Compare 237498123 con 237499123 para la igualdad.
• Decida si 237499123 o 20249472 es más grande.
C++14define la comilla simple ' como separador de dígitos, en números y literales definidos por el
usuario. Esto puede hacer que sea más fácil para los lectores humanos analizar grandes
números.
C ++ 14
Ejemplo:
• Los literales 1048576 , 1'048'576 , 0X100000 , 0x10'0000 y 0'004'000'000 tienen el mismo valor.
• Los literales 1.602'176'565e-19 y 1.602176565e-19 tienen el mismo valor.
La posición de las comillas simples es irrelevante. Todos los siguientes son equivalentes:
C ++ 14
C ++ 14
https://riptutorial.com/es/home 689
Capítulo 117: SFINAE (el fallo de sustitución
no es un error)
Examples
enable_if
es una utilidad conveniente para usar condiciones booleanas para activar SFINAE.
std::enable_if
Se define como:
Es decir, enable_if<true, R>::type es un alias para R , mientras que enable_if<false, T>::type está
mal formado porque la especialización de enable_if no tiene un type miembro de tipo.
Aquí, una llamada a negate(1) fallaría debido a la ambigüedad. Pero la segunda sobrecarga no
está diseñada para usarse con tipos integrales, por lo que podemos agregar:
Cuando usarlo
Vale la pena tener en cuenta que std::enable_if es un ayudante además de SFINAE, pero no es
lo que hace que SFINAE funcione en primer lugar. Consideremos estas dos alternativas para
implementar una funcionalidad similar a std::size , es decir, un size(arg) conjunto de sobrecarga
https://riptutorial.com/es/home 690
size(arg) que produce el tamaño de un contenedor o matriz:
// for containers
template<typename Cont>
auto size1(Cont const& cont) -> decltype( cont.size() );
// for arrays
template<typename Elt, std::size_t Size>
std::size_t size1(Elt const(&arr)[Size]);
// implementation omitted
template<typename Cont>
struct is_sizeable;
// for containers
template<typename Cont, std::enable_if_t<std::is_sizeable<Cont>::value, int> = 0>
auto size2(Cont const& cont);
// for arrays
template<typename Elt, std::size_t Size>
std::size_t size2(Elt const(&arr)[Size]);
Suponiendo que is_sizeable esté escrito correctamente, estas dos declaraciones deberían ser
exactamente equivalentes con respecto a SFINAE. ¿Cuál es la más fácil de escribir y la más fácil
de revisar y entender de un vistazo?
Una vez más, ¿cuál es el más fácil de escribir y cuál es el más fácil de revisar y entender de un
vistazo?
https://riptutorial.com/es/home 691
usaría en varios lugares o no. . Contraste que con std::is_signed que refleja su intención mucho
más claramente que cuando su implementación se filtra en la declaración de incr1 .
void_t
C ++ 11
void_t es una meta-función que mapea cualquier tipo (número de) al tipo void . El propósito
principal de void_t es facilitar la escritura de rasgos de tipo.
std::void_t será parte de C ++ 17, pero hasta entonces, es muy sencillo de implementar:
template <class...>
struct make_void { using type = void; };
La aplicación principal de void_t es escribir rasgos de tipo que comprueban la validez de una
declaración. Por ejemplo, verifiquemos si un tipo tiene una función miembro foo() que no tome
argumentos:
• Si T tiene una función miembro foo() , entonces cualquier tipo que devuelve se convierte a
void , y la especialización se prefiere al primario basado en las reglas de ordenación parcial
de la plantilla. Entonces has_foo<T>::value será true
• Si T no tiene una función miembro de este tipo (o requiere más de un argumento), entonces
la sustitución falla para la especialización y solo tenemos la plantilla principal para el
respaldo. Por lo tanto, has_foo<T>::value es false .
template<class T, class=void>
struct can_reference : std::false_type {};
template<class T>
struct can_reference<T, std::void_t<T&>> : std::true_type {};
https://riptutorial.com/es/home 692
esto no usa std::declval o decltype .
struct details {
template<template<class...>class Z, class=void, class...Ts>
struct can_apply:
std::false_type
{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:
std::true_type
{};
};
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;
que oculta el uso de std::void_t y hace que can_apply actúe como un indicador si el tipo
suministrado como primer argumento de plantilla está bien formado después de sustituir los otros
tipos en él. Los ejemplos anteriores pueden ahora reescribirse usando can_apply como:
template<class T>
using ref_t = T&;
template<class T>
using can_reference = can_apply<ref_t, T>; // Is T& well formed for T?
y:
template<class T>
using dot_foo_r = decltype(std::declval<T&>().foo());
template<class T>
using can_dot_foo = can_apply< dot_foo_r, T >; // Is T.foo() well formed for T?
La utilidad de void_t fue descubierta por Walter Brown. Dio una maravillosa presentación al
respecto en CppCon 2016.
C ++ 11
Una de las funciones de restricción es usar el tipo de decltype final para especificar el tipo de
retorno:
namespace details {
using std::to_string;
https://riptutorial.com/es/home 693
// this one is constrained on being able to call to_string(T)
template <class T>
auto convert_to_string(T const& val, int )
-> decltype(to_string(val))
{
return to_string(val);
}
// this one is unconstrained, but less preferred due to the ellipsis argument
template <class T>
std::string convert_to_string(T const& val, ... )
{
std::ostringstream oss;
oss << val;
return oss.str();
}
}
Que es el SFINAE
SFINAE significa S ubstitution F ailure I s N ot A n E rror. El código mal formado que resulta de la
sustitución de tipos (o valores) para instanciar una plantilla de función o una plantilla de clase no
es un error de compilación, solo se trata como un error de deducción.
int vals[10];
begin(vals); // OK. The first function template substitution fails because
https://riptutorial.com/es/home 694
// vals.begin() is ill-formed. This is not an error! That function
// is just removed from consideration as a viable overload candidate,
// leaving us with the array overload.
Solo las fallas de sustitución en el contexto inmediato se consideran fallas de deducción, todas
las demás se consideran errores graves.
int i = 4;
add_one(i); // ok
enable_if_all / enable_if_any
C ++ 11
Ejemplo motivacional
La biblioteca estándar (antes de C ++ 17) no ofrece una forma directa de escribir enable_if para
imponer restricciones SFINAE en todos los parámetros en Args o cualquiera de los
parámetros en Args . C ++ 17 ofrece std::conjunction y std::disjunction que resuelven este
problema. Por ejemplo:
Si no tiene C ++ 17 disponible, existen varias soluciones para lograrlo. Una de ellas es usar una
clase de caso base y especializaciones parciales , como se demuestra en las respuestas de
esta pregunta .
https://riptutorial.com/es/home 695
, que hacen exactamente lo que se supone que deben hacer semánticamente. Esto
enable_if_any
puede proporcionar una solución más escalable.
template<bool... Bs>
using enable_if_any = std::enable_if<seq_or<Bs...>::value>;
template<bool... Bs>
using enable_if_all = std::enable_if<seq_and<Bs...>::value>;
template<bool... Bs>
using enable_if_any_t = typename enable_if_any<Bs...>::type;
template<bool... Bs>
using enable_if_all_t = typename enable_if_all<Bs...>::type;
Uso
https://riptutorial.com/es/home 696
void func(Args &&...args) { //... };
is_detected
Con los parámetros de la plantilla typename Default , template <typename...> Op y typename ... Args
:
C ++ 17
namespace detail {
template <class Default, class AlwaysVoid,
template<class...> class Op, class... Args>
struct detector
{
using value_t = std::false_type;
using type = Default;
};
} // namespace detail
https://riptutorial.com/es/home 697
Los rasgos para detectar la presencia del método se pueden implementar simplemente:
struct C1 {};
struct C2 {
int foo(char) const;
};
static_assert(!has_foo_char<C1>::value, "Unexpected");
static_assert(has_foo_char<C2>::value, "Unexpected");
static_assert(std::is_same<void, // Default
detected_or<void, foo_type, C1, char>>::value,
"Unexpected");
static_assert(std::is_same<int, detected_or<void, foo_type, C2, char>>::value,
"Unexpected");
Si necesita seleccionar entre varias opciones, habilitar solo una a través de enable_if<> puede ser
bastante engorroso, ya que varias condiciones también deben ser negadas.
En lugar de probar lo que necesita estar bien formado, y también probar la negación de todas las
demás condiciones de la versión, en lugar de eso, probamos solo lo que necesitamos,
preferiblemente en un tipo de decltype en un retorno final.
Esto podría dejar varias opciones bien formadas, diferenciamos entre aquellos que usan
'etiquetas', similares a las etiquetas random_access_tag -rasgo ( random_access_tag et al). Esto
funciona porque una coincidencia directa es mejor que una clase base, que es mejor que una
clase base de una clase base, etc.
#include <algorithm>
#include <iterator>
namespace detail
{
// this gives us infinite types, that inherit from each other
template<std::size_t N>
struct pick : pick<N-1> {};
template<>
struct pick<0> {};
https://riptutorial.com/es/home 698
template<typename T>
auto stable_sort(T& t, pick<2>)
-> decltype( t.stable_sort(), void() )
{
// if the container have a member stable_sort, use that
t.stable_sort();
}
// this is the function the user calls. it will dispatch the call
// to the correct implementation with the help of 'tags'.
template<typename T>
void stable_sort(T& t)
{
// use an N that is higher that any used above.
// this will pick the highest overload that is well formed.
detail::stable_sort(t, detail::pick<10>{});
}
Existen otros métodos que se usan comúnmente para diferenciar las sobrecargas, como que la
coincidencia exacta sea mejor que la conversión, que sea mejor que la elipsis.
https://riptutorial.com/es/home 699
Capítulo 118: Sobrecarga de funciones
Introducción
Véase también el tema separado sobre resolución de sobrecarga
Observaciones
Se pueden producir ambigüedades cuando un tipo puede convertirse implícitamente en más de
un tipo y no hay una función coincidente para ese tipo específico.
Por ejemplo:
Examples
¿Qué es la sobrecarga de funciones?
Supongamos que está escribiendo una serie de funciones para las capacidades de impresión
generalizadas, comenzando con std::string :
Esto funciona bien, pero suponga que quiere una función que también acepte un int y que
también lo imprima. Podrías escribir:
Pero debido a que las dos funciones aceptan diferentes parámetros, simplemente puede escribir:
https://riptutorial.com/es/home 700
{
std::cout << "This is an int: " << num << std::endl;
}
Ahora tiene 2 funciones, ambas con nombre de print , pero con firmas diferentes. Uno acepta
std::string , el otro es int . Ahora puedes llamarlos sin preocuparte por nombres diferentes:
En lugar de:
print("Hello world!");
print_int(1337);
Cuando tiene funciones sobrecargadas, el compilador infiere a cuál de las funciones llamar desde
los parámetros que le proporciona. Se debe tener cuidado al escribir sobrecargas de funciones.
Por ejemplo, con conversiones de tipo implícitas:
Ahora no está claro de inmediato a qué sobrecarga de print se llama cuando escribe:
print(5);
print(static_cast<double>(5));
print(static_cast<int>(5));
print(5.0);
También se debe tener cuidado al escribir sobrecargas que aceptan parámetros opcionales:
// WRONG CODE
void print(int num1, int num2 = 0) //num2 defaults to 0 if not included
{
std::cout << "These are ints: << num1 << " and " << num2 << std::endl;
}
void print(int num)
{
std::cout << "This is an int: " << num << std::endl;
}
Debido a que no hay forma de que el compilador sepa si una llamada como print(17) está
https://riptutorial.com/es/home 701
destinada a la primera o la segunda función debido al segundo parámetro opcional, esto no se
compilará.
Tenga en cuenta que no puede sobrecargar una función en función de su tipo de retorno. Por
ejemplo:
// WRONG CODE
std::string getValue()
{
return "hello";
}
int getValue()
{
return 0;
}
int x = getValue();
Esto provocará un error de compilación, ya que el compilador no podrá determinar a qué versión
de getValue llamar, aunque el tipo de retorno esté asignado a un int .
Las funciones dentro de una clase pueden sobrecargarse cuando se accede a ellas mediante una
referencia calificada por el CV a esa clase; esto es más comúnmente usado para sobrecargar
para const , pero también puede ser usado para sobrecargar para volatile y const volatile . Esto
se debe a que todas las funciones miembro no estáticas toman this como un parámetro oculto, al
que se aplican los calificadores cv. Esto se usa más comúnmente para sobrecargar para const ,
pero también se puede usar para volatile y const volatile .
Esto es necesario porque solo se puede llamar a una función miembro si está al menos tan
calificada como CV como la instancia a la que se llama. Mientras que una instancia no const
puede llamar a miembros const y no const , una instancia const solo puede llamar miembros const
. Esto permite que una función tenga un comportamiento diferente dependiendo de los
calificadores cv de la instancia que llama, y le permite al programador no permitir funciones para
un calificador (es) calificador (es) no deseado (s) al no proporcionar una versión con ese
calificador (es).
Una clase con algunos básicos print método podría ser const sobrecargado de este modo:
#include <iostream>
class Integer
{
public:
Integer(int i_): i{i_}{}
void print()
{
https://riptutorial.com/es/home 702
std::cout << "int: " << i << std::endl;
}
protected:
int i;
};
int main()
{
Integer i{5};
const Integer &ic = i;
Este es un principio clave de la corrección de const : Al marcar las funciones miembro como const
, se les permite llamar en instancias const , lo que a su vez permite que las funciones tomen
instancias como punteros / referencias const si no necesitan modificarlas. Esto permite que el
código especifique si modifica el estado tomando parámetros no modificados como const y
parámetros modificados sin calificadores cv, lo que hace que el código sea más seguro y más
legible.
class ConstCorrect
{
public:
void good_func() const
{
std::cout << "I care not whether the instance is const." << std::endl;
}
void bad_func()
{
std::cout << "I can only be called on non-const, non-volatile instances." <<
std::endl;
}
};
Un uso común de esto es declarar los accesores como const , y los mutadores como no const .
https://riptutorial.com/es/home 703
Ningún miembro de clase puede ser modificado dentro de una función miembro const . Si hay
algún miembro que realmente necesita modificar, como bloquear un std::mutex , puede declararlo
como mutable :
class Integer
{
public:
Integer(int i_): i{i_}{}
protected:
int i;
mutable std::mutex mut;
};
https://riptutorial.com/es/home 704
Capítulo 119: Sobrecarga del operador
Introducción
En C ++, es posible definir operadores como + y -> para tipos definidos por el usuario. Por
ejemplo, el encabezado <string> define un operador + para concatenar cadenas. Esto se hace
definiendo una función de operador utilizando la palabra clave del operator .
Observaciones
Los operadores para los tipos incorporados no se pueden cambiar, los operadores solo se
pueden sobrecargar para los tipos definidos por el usuario. Es decir, al menos uno de los
operandos debe ser de un tipo definido por el usuario.
¿Por qué? Porque sobrecargan a los operadores que otro programador nunca esperaría, lo que
resulta en un comportamiento diferente al anticipado.
Por ejemplo, el usuario definió && y || sobrecargas de estos operadores pierden su evaluación de
cortocircuito y pierden sus propiedades de secuenciación especiales (C ++ 17) , la cuestión de la
secuencia también se aplica a , sobrecargas de operadores.
Examples
Operadores aritméticos
• + y +=
• - y -=
• * y *=
https://riptutorial.com/es/home 705
• / y /=
• & y &=
• | y |=
• ^ y ^=
• >> y >>=
• << y <<=
La sobrecarga para todos los operadores es la misma. Desplácese hacia abajo para la
explicación
Nota: el operator+ debe devolver un valor no constante, ya que devolver una referencia no tendría
sentido (devuelve un nuevo objeto) ni tampoco devolvería un valor const (por lo general, no
debería devolver por const ). El primer argumento se pasa por valor, ¿por qué? Porque
1. No puede modificar el objeto original ( Object foobar = foo + bar; no debería modificar foo
después de todo, no tendría sentido)
2. No puede hacer que sea const , porque tendrá que poder modificar el objeto (porque el
operator+ se implementa en términos de operator+= , que modifica el objeto)
Pasar por const& sería una opción, pero luego tendrá que hacer una copia temporal del objeto
pasado. Al pasar por valor, el compilador lo hace por ti.
https://riptutorial.com/es/home 706
operator+=devuelve una referencia al mismo, porque es posible encadenarlos (aunque no use la
misma variable, sería un comportamiento indefinido debido a los puntos de secuencia).
El primer argumento es una referencia (queremos modificarlo), pero no const , porque entonces
no podrías modificarlo. El segundo argumento no debe modificarse, por lo que, por razones de
rendimiento, pasa por const& (pasar por const es más rápido que por valor).
Operadores unarios
• ++foo y foo++
• --foo y foo--
//Postfix operator foo++ (int argument is used to separate pre- and postfix)
//Should be implemented in terms of ++foo (prefix operator)
T operator++(T& lhs, int)
{
T t(lhs);
++lhs;
return t;
}
//Postfix operator foo++ (int argument is used to separate pre- and postfix)
//Should be implemented in terms of ++foo (prefix operator)
T operator++(int)
{
T t(*this);
++(*this);
return t;
}
Nota: El operador de prefijo devuelve una referencia a sí mismo, para que pueda continuar las
https://riptutorial.com/es/home 707
operaciones en él. El primer argumento es una referencia, ya que el operador de prefijo cambia el
objeto, esa es también la razón por la que no es const (de lo contrario no podría modificarlo).
El operador de postfix devuelve por valor un temporal (el valor anterior), por lo que no puede ser
una referencia, ya que sería una referencia a un temporal, que sería un valor de basura al final de
la función, porque la variable temporal se apaga. de alcance). Tampoco puede ser const , porque
deberías poder modificarlo directamente.
El primer argumento es una referencia no const al objeto "llamante", porque si fuera const , no
sería capaz de modificarlo, y si no fuera una referencia, no cambiaría el valor original.
Es debido a la copia necesaria en las sobrecargas de operadores de postfix que es mejor hacer
que sea un hábito usar el prefijo ++ en lugar de postfix ++ en bucles for . Desde la perspectiva del
bucle for , por lo general son funcionalmente equivalentes, pero puede haber una ligera ventaja
de rendimiento al usar el prefijo ++, especialmente con clases "grandes" con muchos miembros
para copiar. Ejemplo de uso del prefijo ++ en un bucle for:
Operadores de comparación
• == y !=
• >y<
• >= y <=
//Note that the functions are const, because if they are not const, you wouldn't be able
https://riptutorial.com/es/home 708
//to call them if the object is const
Los operadores obviamente devuelven un bool , indicando true o false para la operación
correspondiente.
Todos los operadores toman sus argumentos por const& , porque lo único que hacen los
operadores es comparar, por lo que no deben modificar los objetos. Pasar por & (referencia) es
más rápido que por valor, y para asegurarse de que los operadores no lo modifiquen, es una
referencia const .
Tenga en cuenta que los operadores dentro de la class / struct se definen como const , la razón
de esto es que sin las funciones const , no sería posible comparar objetos const , ya que el
compilador no sabe que los operadores no modifican nada.
Operadores de conversión
Puede sobrecargar los operadores de tipo, de modo que su tipo pueda convertirse implícitamente
en el tipo especificado.
Ejemplo:
struct Text
{
std::string text;
Text t;
t.text = "Hello world!";
//Ok
const char* copyoftext = t;
https://riptutorial.com/es/home 709
Operador de subíndice de matriz
Siempre debe (99,98% de las veces) implemente 2 versiones, una const y un no- const versión,
ya que si el objeto es const , no debería ser capaz de modificar el objeto devuelto por [] .
Los argumentos se pasan por const& lugar de por valor porque pasar por referencia es más rápido
que por valor, y const para que el operador no cambie el índice accidentalmente.
Los operadores devuelven por referencia, porque por diseño puede modificar la devolución del
objeto [] , es decir:
std::vector<int> v{ 1 };
v[0] = 2; //Changes value of 1 to 2
//wouldn't be possible if not returned by reference
template<class T>
class matrix {
// class enabling [][] overload to access matrix elements
template <class C>
class proxy_row_vector {
using reference = decltype(std::declval<C>()[0]);
using const_reference = decltype(std::declval<C const>()[0]);
public:
proxy_row_vector(C& _vec, std::size_t _r_ind, std::size_t _cols)
: vec(_vec), row_index(_r_ind), cols(_cols) {}
const_reference operator[](std::size_t _col_index) const {
return vec[row_index*cols + _col_index];
}
reference operator[](std::size_t _col_index) {
return vec[row_index*cols + _col_index];
}
private:
https://riptutorial.com/es/home 710
C& vec;
std::size_t row_index; // row index to access
std::size_t cols; // number of columns in matrix
};
Por ejemplo:
struct Sum
{
int operator()(int a, int b)
{
return a + b;
}
};
https://riptutorial.com/es/home 711
Operador de asignación
El operador de asignación es uno de los operadores más importantes porque le permite cambiar
el estado de una variable.
Sobrecargar el operador de asignación = es fácil, pero debe seguir algunos pasos simples.
Nota: other se pasa por const& , porque el objeto que se asigna no debe cambiarse, y pasar por
referencia es más rápido que por valor, y para asegurarse de que operator= no lo modifique
accidentalmente, es const .
Sobrecargar el bit a bit ( ~ ) es bastante simple. Desplácese hacia abajo para la explicación
https://riptutorial.com/es/home 712
Sobrecarga fuera de class / struct :
T operator~(T lhs)
{
//Do operation
return lhs;
}
T operator~()
{
T t(*this);
//Do operation
return t;
}
Nota: operator~ devuelve por valor, porque tiene que devolver un nuevo valor (el valor
modificado), y no una referencia al valor (sería una referencia al objeto temporal, que tendría un
valor de basura en él tan pronto como el operador está hecho). const porque el código de llamada
debe poder modificarlo posteriormente (es decir, int a = ~a + 1; debería ser posible).
Dentro de la class / struct usted tiene que hacer un objeto temporal, porque no se puede
modificar this , ya que modificaría el objeto original, que no debería ser el caso.
Los operadores << y >> se utilizan comúnmente como operadores de "escritura" y "lectura":
• std::ostream overloads << para escribir variables en el flujo subyacente (ejemplo: std::cout )
• std::istream overloads >> para leer desde el flujo subyacente a una variable (ejemplo:
std::cin )
• Tipo de retorno es el flujo del que desea sobrecargar (por ejemplo, std::ostream ) pasado
por referencia, para permitir el encadenamiento (Encadenamiento: std::cout << a << b; ).
Ejemplo: std::ostream&
• lhs sería el mismo que el tipo de retorno
• rhs es el tipo desde el que desea permitir la sobrecarga (es decir, T ), pasado por const&
lugar de valor por razones de rendimiento ( rhs no debería cambiarse de todos modos).
Ejemplo: const Vector& .
Ejemplo:
https://riptutorial.com/es/home 713
return lhs;
}
Vector v = { 1, 2, 3};
El siguiente código implementa un tipo de número complejo muy simple para el cual el campo
subyacente se promueve automáticamente, siguiendo las reglas de promoción del tipo de idioma,
bajo la aplicación de los cuatro operadores básicos (+, -, * y /) con un miembro de un campo
diferente (ya sea otro complex<T> o algún tipo escalar).
Se pretende que sea un ejemplo holístico que cubra la sobrecarga de operadores junto con el uso
básico de plantillas.
#include <type_traits>
namespace not_std{
using std::decay_t;
//----------------------------------------------------------------
// complex< value_t >
//----------------------------------------------------------------
template<typename value_t>
struct complex
{
value_t x;
value_t y;
https://riptutorial.com/es/home 714
complex &operator *= (const value_t &s)
{
this->x *= s;
this->y *= s;
return *this;
}
complex &operator *= (const complex &other)
{
(*this) = (*this) * other;
return *this;
}
template<typename other_value_t>
explicit complex(const complex<other_value_t> &other)
: x{static_cast<const value_t &>(other.x)}
, y{static_cast<const value_t &>(other.y)}
{}
//----------------------------------------------------------------
// operator - (negation)
//----------------------------------------------------------------
template<typename value_t>
complex<value_t> operator - (const complex<value_t> &z)
{ return {-z.x, -z.y}; }
//----------------------------------------------------------------
// operator +
//----------------------------------------------------------------
https://riptutorial.com/es/home 715
auto operator + (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x + b.x)>>
{ return{a.x + b.x, a.y + b.y}; }
//----------------------------------------------------------------
// operator -
//----------------------------------------------------------------
//----------------------------------------------------------------
// operator *
//----------------------------------------------------------------
//----------------------------------------------------------------
// operator /
//----------------------------------------------------------------
https://riptutorial.com/es/home 716
template<typename left_t, typename right_t>
auto operator / (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x / b.x)>>
{
const auto r = absqr(b);
return {
( a.x*b.x + a.y*b.y) / r,
(-a.x*b.y + a.y*b.x) / r
};
}
// makes a complex<double>
auto dz = fz * 1.0;
// still a complex<double>
auto idz = 1.0f/dz;
// also a complex<double>
auto one = dz * idz;
// a complex<double> again
auto one_again = fz * idz;
https://riptutorial.com/es/home 717
auto d1 = 1 + a;
auto d2 = a - 1;
auto d3 = 1 - a;
auto d4 = a * 1;
auto d5 = 1 * a;
auto d6 = a / 1;
auto d7 = 1 / a;
return 0;
}
Operadores nombrados
Puede extender C ++ con operadores nombrados que están "cotizados" por los operadores
estándar de C ++.
namespace named_operator {
template<class D>struct make_operator{constexpr make_operator(){}};
namespace my_ns {
struct append_t : named_operator::make_operator<append_t> {};
constexpr append_t append{};
https://riptutorial.com/es/home 718
lhs.insert( lhs.end(), rhs.begin(), rhs.end() );
return std::move(lhs);
}
}
using my_ns::append;
std::vector<int> a {1,2,3};
std::vector<int> b {4,5,6};
auto c = a *append* b;
Luego sobrecargamos named_invoke (lhs, append_t, rhs) para los tipos que queremos a la
derecha e izquierda.
Simplemente tenemos que crear el token append_t adecuado y hacer un named_invoke ADL de la
firma adecuada, y todo se conecta y funciona.
Para un ejemplo más complejo, suponga que quiere tener una multiplicación de elementos de un
std :: array:
template<class=void, std::size_t...Is>
auto indexer( std::index_sequence<Is...> ) {
return [](auto&& f) {
return f( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto indexer() { return indexer( std::make_index_sequence<N>{} ); }
namespace my_ns {
struct e_times_t : named_operator::make_operator<e_times_t> {};
constexpr e_times_t e_times{};
ejemplo vivo .
https://riptutorial.com/es/home 719
Este código de matriz de elementos se puede ampliar para trabajar en tuplas o pares o matrices
de estilo C, o incluso contenedores de longitud variable si decide qué hacer si las longitudes no
coinciden.
También puede lhs *element_wise<'+'>* rhs tipo de operador inteligente y obtener lhs
*element_wise<'+'>* rhs .
El uso de * se puede extender para admitir otros delimitadores, como + . La precisión del
delimitador determina la precisión del operador nombrado, lo que puede ser importante al traducir
las ecuaciones físicas a C ++ con un uso mínimo de extra () s.
Con un ligero cambio en la biblioteca anterior, podemos admitir ->*then* operadores y extender la
std::function antes de que se actualice el estándar, o escribir de forma monádica ->*bind* .
También podría tener un operador con nombre de estado, donde pasamos cuidadosamente la Op
hasta la función de invocación final, permitiendo:
https://riptutorial.com/es/home 720
Capítulo 120: static_assert
Sintaxis
• static_assert ( bool_constexpr , mensaje )
• static_assert ( bool_constexpr ) / * Desde C ++ 17 * /
Parámetros
Parámetro Detalles
Observaciones
A diferencia de las aserciones en tiempo de ejecución , las aserciones estáticas se verifican en
tiempo de compilación y también se aplican cuando se compilan construcciones optimizadas.
Examples
static_assert
Las afirmaciones significan que una condición debe ser verificada y si es falsa, es un error. Para
static_assert() , esto se hace en tiempo de compilación.
template<typename T>
T mul10(const T t)
{
static_assert( std::is_integral<T>::value, "mul10() only works for integral types" );
return (t << 3) + (t << 1);
}
C ++ 17
template<typename T>
T mul10(const T t)
{
static_assert(std::is_integral<T>::value);
return (t << 3) + (t << 1);
}
https://riptutorial.com/es/home 721
Se utiliza cuando:
Tenga en cuenta que static_assert() no participa en SFINAE : por lo tanto, cuando son posibles
sobrecargas / especializaciones adicionales, no se debe usar en lugar de técnicas de
metaprogramación de plantillas (como std::enable_if<> ). Puede usarse en el código de la plantilla
cuando ya se encuentra la sobrecarga / especialización esperada, pero se requieren
verificaciones adicionales. En tales casos, podría proporcionar mensajes de error más concretos
que confiar en SFINAE para esto.
https://riptutorial.com/es/home 722
Capítulo 121: std :: array
Parámetros
Parámetro Definición
Observaciones
El uso de un std::array requiere la inclusión del encabezado <array> usando #include <array> .
Examples
Inicializando un std :: array
// 1) Using aggregate-initialization
std::array<int, 3> a{ 0, 1, 2 };
// or equivalently
std::array<int, 3> a = { 0, 1, 2 };
https://riptutorial.com/es/home 723
// or equivalently
std::array<A, 2> a = { 0, 1, 2, 3, 4, 5 };
// 3)
std::array<A, 2> a{{ { 0, 1, 2 }, { 3, 4, 5 } }};
// or equivalently
std::array<A, 2> a = {{ { 0, 1, 2 }, { 3, 4, 5 } }};
Acceso a elementos
1. at(pos)
Devuelve una referencia al elemento en la posición pos con comprobación de límites. Si pos no
está dentro del rango del contenedor, se lanza una excepción de tipo std::out_of_range .
#include <array>
int main()
{
std::array<int, 3> arr;
// write values
arr.at(0) = 2;
arr.at(1) = 4;
arr.at(2) = 6;
// read values
int a = arr.at(0); // a is now 2
int b = arr.at(1); // b is now 4
int c = arr.at(2); // c is now 6
return 0;
}
2) operator[pos]
Devuelve una referencia al elemento en la posición pos sin verificación de límites. Si pos no está
dentro del rango del contenedor, puede ocurrir un error de violación de segmentación en tiempo
de ejecución. Este método proporciona acceso a elementos equivalente a las matrices clásicas y,
por lo tanto, más eficiente que at(pos) .
https://riptutorial.com/es/home 724
La complejidad es constante O (1).
#include <array>
int main()
{
std::array<int, 3> arr;
// write values
arr[0] = 2;
arr[1] = 4;
arr[2] = 6;
// read values
int a = arr[0]; // a is now 2
int b = arr[1]; // b is now 4
int c = arr[2]; // c is now 6
return 0;
}
3) std::get<pos>
Esta función que no es miembro devuelve una referencia al elemento en la posición constante
en tiempo de compilación pos sin verificación de límites. Si pos no está dentro del rango del
contenedor, puede ocurrir un error de violación de segmentación en tiempo de ejecución.
#include <array>
int main()
{
std::array<int, 3> arr;
// write values
std::get<0>(arr) = 2;
std::get<1>(arr) = 4;
std::get<2>(arr) = 6;
// read values
int a = std::get<0>(arr); // a is now 2
int b = std::get<1>(arr); // b is now 4
int c = std::get<2>(arr); // c is now 6
return 0;
}
4) front()
https://riptutorial.com/es/home 725
#include <array>
int main()
{
std::array<int, 3> arr{ 2, 4, 6 };
return 0;
}
5) back()
#include <array>
int main()
{
std::array<int, 3> arr{ 2, 4, 6 };
return 0;
}
6) data()
#include <iostream>
#include <cstring>
#include <array>
int main ()
{
const char* cstr = "Test string";
std::array<char, 12> arr;
return 0;
}
https://riptutorial.com/es/home 726
Una de las principales ventajas de std::array en comparación con la matriz de estilo C es que
podemos verificar el tamaño de la matriz utilizando la función miembro size()
int main() {
std::array<int, 3> arr = { 1, 2, 3 };
cout << arr.size() << endl;
}
int main() {
std::array<int, 3> arr = { 1, 2, 3 };
for (auto i : arr)
cout << i << '\n';
}
La función miembro fill() se puede usar en std::array para cambiar los valores a la vez después
de la inicialización
int main() {
https://riptutorial.com/es/home 727
Capítulo 122: std :: atómica
Examples
tipos atómicos
Además, los accesos a objetos atómicos pueden establecer la sincronización entre subprocesos y
ordenar accesos de memoria no atómica según lo especificado por std::memory_order .
std :: atomic se puede crear una instancia con cualquier TriviallyCopyable type T. std::atomic no
se puede copiar ni mover.
1. Se define una especialización completa para el tipo bool y su nombre typedef que se trata
como un std::atomic<T> no especializado, excepto que tiene un diseño estándar, un
constructor predeterminado trivial, un destructor trivial y soporta la sintaxis de inicialización
agregada:
std::atomic_bool std::atomic<bool>
std::atomic_char std::atomic<char>
std::atomic_char std::atomic<char>
std::atomic_short std::atomic<short>
std::atomic_int std::atomic<int>
std::atomic_long std::atomic<long>
https://riptutorial.com/es/home 728
Nombre de typedef Especialización completa
std::atomic_char16_t std::atomic<char16_t>
std::atomic_char32_t std::atomic<char32_t>
std::atomic_wchar_t std::atomic<wchar_t>
std::atomic_int8_t std::atomic<std::int8_t>
std::atomic_uint8_t std::atomic<std::uint8_t>
std::atomic_int16_t std::atomic<std::int16_t>
std::atomic_uint16_t std::atomic<std::uint16_t>
std::atomic_int32_t std::atomic<std::int32_t>
std::atomic_uint32_t std::atomic<std::uint32_t>
std::atomic_int64_t std::atomic<std::int64_t>
std::atomic_uint64_t std::atomic<std::uint64_t>
std::atomic_int_least8_t std::atomic<std::int_least8_t>
std::atomic_uint_least8_t std::atomic<std::uint_least8_t>
std::atomic_int_least16_t std::atomic<std::int_least16_t>
std::atomic_uint_least16_t std::atomic<std::uint_least16_t>
std::atomic_int_least32_t std::atomic<std::int_least32_t>
std::atomic_uint_least32_t std::atomic<std::uint_least32_t>
std::atomic_int_least64_t std::atomic<std::int_least64_t>
std::atomic_uint_least64_t std::atomic<std::uint_least64_t>
std::atomic_int_fast8_t std::atomic<std::int_fast8_t>
std::atomic_uint_fast8_t std::atomic<std::uint_fast8_t>
std::atomic_int_fast16_t std::atomic<std::int_fast16_t>
std::atomic_uint_fast16_t std::atomic<std::uint_fast16_t>
std::atomic_int_fast32_t std::atomic<std::int_fast32_t>
std::atomic_uint_fast32_t std::atomic<std::uint_fast32_t>
std::atomic_int_fast64_t std::atomic<std::int_fast64_t>
std::atomic_uint_fast64_t std::atomic<std::uint_fast64_t>
std::atomic_intptr_t std::atomic<std::intptr_t>
https://riptutorial.com/es/home 729
Nombre de typedef Especialización completa
std::atomic_uintptr_t std::atomic<std::uintptr_t>
std::atomic_size_t std::atomic<std::size_t>
std::atomic_ptrdiff_t std::atomic<std::ptrdiff_t>
std::atomic_intmax_t std::atomic<std::intmax_t>
std::atomic_uintmax_t std::atomic<std::uintmax_t>
void set_foo(int x) {
foo.store(x,std::memory_order_relaxed); // set value atomically
}
void print_foo() {
int x;
do {
x = foo.load(std::memory_order_relaxed); // get value atomically
} while (x==0);
std::cout << "foo: " << x << '\n';
}
int main ()
{
std::thread first (print_foo);
std::thread second (set_foo,10);
first.join();
//second.join();
return 0;
}
//output: foo: 10
https://riptutorial.com/es/home 730
Capítulo 123: std :: cualquiera
Observaciones
La clase std::any proporciona un contenedor seguro de tipos en el que podemos poner valores
únicos de cualquier tipo.
Examples
Uso básico
try {
std::any_cast<int>(an_object);
} catch(std::bad_any_cast&) {
std::cout << "Wrong type\n";
}
std::any_cast<std::string&>(an_object) = "42";
std::cout << std::any_cast<std::string>(an_object) << '\n';
Salida
hello world
Wrong type
42
https://riptutorial.com/es/home 731
Capítulo 124: std :: forward_list
Introducción
std::forward_listes un contenedor que admite la inserción y eliminación rápida de elementos
desde cualquier parte del contenedor. El acceso aleatorio rápido no es compatible. Se
implementa como una lista enlazada individualmente y, en esencia, no tiene ningún gasto general
en comparación con su implementación en C. Comparado con std::list este contenedor
proporciona un almacenamiento más eficiente en espacio cuando no se necesita la iteración
bidireccional.
Observaciones
Agregar, eliminar y mover los elementos dentro de la lista, o en varias listas, no invalida los
iteradores que actualmente hacen referencia a otros elementos en la lista. Sin embargo, un
iterador o referencia que se refiere a un elemento se invalida cuando el elemento correspondiente
se elimina (a través de erase_after) de la lista. std :: forward_list cumple con los requisitos de
Container (excepto la función de miembro de tamaño y la complejidad de ese operador == es
siempre lineal), AllocatorAwareContainer y SequenceContainer.
Examples
Ejemplo
#include <forward_list>
#include <string>
#include <iostream>
template<typename T>
std::ostream& operator<<(std::ostream& s, const std::forward_list<T>& v) {
s.put('[');
char comma[3] = {'\0', ' ', '\0'};
for (const auto& e : v) {
s << comma << e;
comma[0] = ',';
}
return s << ']';
}
int main()
{
// c++11 initializer list syntax:
std::forward_list<std::string> words1 {"the", "frogurt", "is", "also", "cursed"};
std::cout << "words1: " << words1 << '\n';
// words2 == words1
std::forward_list<std::string> words2(words1.begin(), words1.end());
std::cout << "words2: " << words2 << '\n';
// words3 == words1
https://riptutorial.com/es/home 732
std::forward_list<std::string> words3(words1);
std::cout << "words3: " << words3 << '\n';
Salida:
Métodos
------ ------
Acceso a elementos
------ ------
Iteradores
Capacidad
https://riptutorial.com/es/home 733
Nombre del método Definición
Modificadores
Operaciones
https://riptutorial.com/es/home 734
Capítulo 125: std :: function: Para envolver
cualquier elemento que sea llamable
Examples
Uso simple
#include <iostream>
#include <functional>
std::function<void(int , const std::string&)> myFuncObj;
void theFunc(int i, const std::string& s)
{
std::cout << s << ": " << i << std::endl;
}
int main(int argc, char *argv[])
{
myFuncObj = theFunc;
myFuncObj(10, "hello world");
}
Piense en una situación en la que necesitamos devolver una función con argumentos.
std::function utilizada con std::bind proporciona una construcción de diseño muy potente como
se muestra a continuación.
class A
{
public:
std::function<void(int, const std::string&)> m_CbFunc = nullptr;
void foo()
{
if (m_CbFunc)
{
m_CbFunc(100, "event fired");
}
}
};
class B
{
public:
B()
{
auto aFunc = std::bind(&B::eventHandler, this, std::placeholders::_1,
std::placeholders::_2);
anObjA.m_CbFunc = aFunc;
}
void eventHandler(int i, const std::string& s)
{
std::cout << s << ": " << i << std::endl;
https://riptutorial.com/es/home 735
}
void DoSomethingOnA()
{
anObjA.foo();
}
A anObjA;
};
#include <iostream>
#include <functional>
int main()
{
int a = 2;
/* Function pointers */
std::cout << stdf_foobar(a, &foo) << std::endl; // 6 ( 2 + (2+2) )
// can also be: stdf_foobar(2, foo)
/* Lambda expressions */
/* An unnamed closure from a lambda expression can be
* stored in a std::function object:
*/
int capture_value = 3;
std::cout << stdf_foobar(a,
[capture_value](int param) -> int { return 7 + capture_value *
param; })
<< std::endl;
// result: 15 == value + (7 * capture_value * value) == 2 + (7 + 3 * 2)
/* std::bind expressions */
/* The result of a std::bind expression can be passed.
* For example by binding parameters to a function pointer call:
*/
int b = stdf_foobar(a, std::bind(foo_2, _1, 3));
std::cout << b << std::endl;
// b == 23 == 2 + ( 9*2 + 3 )
int c = stdf_foobar(a, std::bind(foo_2, 5, _1));
https://riptutorial.com/es/home 736
std::cout << c << std::endl;
// c == 49 == 2 + ( 9*5 + 2 )
return 0;
}
`function` sobrecarga
std::function puede causar una sobrecarga significativa. Debido a que std::function tiene [valor
semántico] [1], debe copiar o mover el llamable dado en sí mismo. Pero como puede tomar
callables de un tipo arbitrario, con frecuencia tendrá que asignar memoria dinámicamente para
hacer esto.
Considera lo siguiente:
//Header file
using MyPredicate = std::function<bool(const MyValue &, const MyValue &)>;
//Source file
void SortMyContainer(MyContainer &C, const MyPredicate &pred)
{
std::sort(C.begin(), C.end(), pred);
}
Un parámetro de plantilla sería la solución preferida para SortMyContainer , pero supongamos que
esto no es posible o deseable por cualquier motivo. SortMyContainer no necesita almacenar pred
más allá de su propia llamada. Y, sin embargo, pred puede asignar memoria si el functor que se le
asigna es de algún tamaño no trivial.
function asigna memoria porque necesita algo para copiar / mover; function se apropia de lo
callable que se le da. Pero SortMyContainer no necesita poseer el invocable; es solo referenciarlo
Así que usar la function aquí es una exageración; Puede ser eficiente, pero puede no serlo.
No hay un tipo de función de biblioteca estándar que simplemente haga referencia a un llamable.
Por lo tanto, habrá que encontrar una solución alternativa, o puede elegir vivir con los gastos
generales.
Además, la function no tiene medios efectivos para controlar de dónde provienen las
asignaciones de memoria para el objeto. Sí, tiene constructores que toman un allocator , pero
[muchas implementaciones no los implementan correctamente ... o incluso en absoluto ] [2].
C ++ 17
https://riptutorial.com/es/home 737
Los constructores de function que toman un allocator ya no forman parte del tipo. Por lo tanto, no
hay manera de gestionar la asignación.
Llamar a una function también es más lento que llamar directamente al contenido. Dado que
cualquier instancia de function podría contener cualquier llamada, la llamada a través de una
function debe ser indirecta. La sobrecarga de la function de llamada está en el orden de una
llamada de función virtual.
/*
* This example show some ways of using std::function to call
* a) C-like function
* b) class-member function
* c) operator()
* d) lambda function
*
* Function call can be made:
* a) with right arguments
* b) argumens with different order, types and count
*/
#include <iostream>
#include <functional>
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using namespace std::placeholders;
https://riptutorial.com/es/home 738
double foo_fn_4(int x, double z, float y, long xx)
{
double res = x + y + z + xx;
std::cout << "foo_struct::foo_fn_4 called with arguments: "
<< x << ", " << z << ", " << y << ", " << xx
<< " result is : " << res
<< std::endl;
return res;
}
// overloaded operator() makes whole object to be callable
double operator()(int x, float y, double z)
{
double res = x + y + z;
std::cout << "foo_struct::operator() called with arguments: "
<< x << ", " << y << ", " << z
<< " result is : " << res
<< std::endl;
return res;
}
};
int main(void)
{
// typedefs
using function_type = std::function<double(int, float, double)>;
// foo_struct instance
foo_struct fs;
https://riptutorial.com/es/home 739
std::cout << "Test stored functions with arguments: x = 1, y = 2, z = 3"
<< std::endl;
Vivir
Salida:
Algunos programas necesitan almacenar los argumentos para futuras llamadas de alguna
función.
Este ejemplo muestra cómo llamar a cualquier función con argumentos almacenados en std ::
tuple
#include <iostream>
#include <functional>
#include <tuple>
#include <iostream>
// invocation helper
template<typename FN, typename P, int ...S>
double call_fn_internal(const FN& fn, const P& params, const seq<S...>)
{
return fn(std::get<S>(params) ...);
}
// call function with arguments stored in std::tuple
template<typename Ret, typename ...Args>
Ret call_fn(const std::function<Ret(Args...)>& fn,
https://riptutorial.com/es/home 740
const std::tuple<Args...>& params)
{
return call_fn_internal(fn, params, typename gens<sizeof...(Args)>::type());
}
int main(void)
{
// arguments
std::tuple<int, float, double> t = std::make_tuple(1, 5, 10);
// function to call
std::function<double(int, float, double)> fn = foo_fn;
Vivir
Salida:
Lea std :: function: Para envolver cualquier elemento que sea llamable en línea:
https://riptutorial.com/es/cplusplus/topic/2294/std----function--para-envolver-cualquier-elemento-
que-sea-llamable
https://riptutorial.com/es/home 741
Capítulo 126: std :: integer_sequence
Introducción
La plantilla de clase std::integer_sequence<Type, Values...> representa una secuencia de valores
de tipo Type donde Type es uno de los tipos de enteros incorporados. Estas secuencias se utilizan
al implementar plantillas de clase o función que se benefician del acceso posicional. La biblioteca
estándar también contiene tipos "de fábrica" que crean secuencias ascendentes de valores
enteros solo a partir del número de elementos.
Examples
Gire un std :: tuple en parámetros de función
Se puede usar un std::tuple<T...> para pasar varios valores. Por ejemplo, podría usarse para
almacenar una secuencia de parámetros en alguna forma de una cola. Al procesar tal tupla, sus
elementos deben convertirse en argumentos de llamada de función:
#include <array>
#include <iostream>
#include <string>
#include <tuple>
#include <utility>
// ----------------------------------------------------------------------------
// Example functions to be called:
void f(int i, std::string const& s) {
std::cout << "f(" << i << ", " << s << ")\n";
}
void f(int i, double d, std::string const& s) {
std::cout << "f(" << i << ", " << d << ", " << s << ")\n";
}
void f(char c, int i, double d, std::string const& s) {
std::cout << "f(" << c << ", " << i << ", " << d << ", " << s << ")\n";
}
void f(int i, int j, int k) {
std::cout << "f(" << i << ", " << j << ", " << k << ")\n";
}
// ----------------------------------------------------------------------------
// The actual function expanding the tuple:
template <typename Tuple, std::size_t... I>
void process(Tuple const& tuple, std::index_sequence<I...>) {
f(std::get<I>(tuple)...);
}
https://riptutorial.com/es/home 742
// ----------------------------------------------------------------------------
int main() {
process(std::make_tuple(1, 3.14, std::string("foo")));
process(std::make_tuple('a', 2, 2.71, std::string("bar")));
process(std::make_pair(3, std::string("pair")));
process(std::array<int, 3>{ 1, 2, 3 });
}
#include <iostream>
#include <initializer_list>
#include <utility>
int main() {
// explicitly specify sequences:
print_sequence(std::integer_sequence<int, 1, 2, 3>());
print_sequence(std::integer_sequence<char, 'f', 'o', 'o'>());
// generate sequences:
print_sequence(std::make_index_sequence<10>());
print_sequence(std::make_integer_sequence<short, 10>());
print_offset_sequence<'A'>(std::make_integer_sequence<char, 26>());
}
La expansión del paquete de parámetros de índices en una expresión de coma con un valor crea
una copia del valor para cada uno de los índices. Lamentablemente, gcc y el clang pensar en el
índice no tiene ningún efecto y advertir de ello ( gcc se puede silenciar el índice de fundición a void
https://riptutorial.com/es/home 743
):
#include <algorithm>
#include <array>
#include <iostream>
#include <iterator>
#include <string>
#include <utility>
int main() {
auto array = make_array<20>(std::string("value"));
std::copy(array.begin(), array.end(),
std::ostream_iterator<std::string>(std::cout, " "));
std::cout << "\n";
}
https://riptutorial.com/es/home 744
Capítulo 127: std :: iomanip
Examples
std :: setw
Esto produce:
10
10
1234567890
(donde está la última línea para ayudar a ver las compensaciones del personaje).
std::setw(int n)
std :: setprecision
Cuando se usa en una expresión out << setprecision(n) o in >> setprecision(n) , establece el
parámetro de precisión de la transmisión o en n exactamente. El parámetro de esta función es
entero, que es un nuevo valor para la precisión.
Ejemplo:
#include <iostream>
#include <iomanip>
#include <cmath>
#include <limits>
int main()
{
const long double pi = std::acos(-1.L);
std::cout << "default precision (6): " << pi << '\n'
<< "std::precision(10): " << std::setprecision(10) << pi << '\n'
<< "max precision: "
<< std::setprecision(std::numeric_limits<long double>::digits10 + 1)
https://riptutorial.com/es/home 745
<< pi << '\n';
}
//Output
//default precision (6): 3.14159
//std::precision(10): 3.141592654
//max precision: 3.141592653589793239
std :: setfill
Cuando se utiliza en una expresión out << setfill(c) establece el carácter de relleno de la
transmisión en c .
Ejemplo:
#include <iostream>
#include <iomanip>
int main()
{
std::cout << "default fill: " << std::setw(10) << 42 << '\n'
<< "setfill('*'): " << std::setfill('*')
<< std::setw(10) << 42 << '\n';
}
//output::
//default fill: 42
//setfill('*'): ********42
std :: setiosflags
Cuando se utiliza en una expresión out << setiosflags(mask) o in >> setiosflags(mask) , establece
todos los indicadores de formato de la transmisión hacia fuera o dentro como se especifica en la
máscara.
https://riptutorial.com/es/home 746
• showbase - genera un prefijo que indica la base numérica para la salida de enteros, requiere
el indicador de moneda en I / O monetaria
• showpoint : genere incondicionalmente un carácter de punto decimal para la salida del
número de punto flotante
• showpos - genera un carácter + para salida numérica no negativa
• skipws : omite los espacios en blanco skipws antes de ciertas operaciones de entrada
• unitbuf descarga la salida después de cada operación de salida
• uppercase : reemplace ciertas letras minúsculas con sus equivalentes en mayúsculas en
ciertas operaciones de salida de salida
Ejemplo de manipuladores:
#include <iostream>
#include <string>
#include<iomanip>
int main()
{
int l_iTemp = 47;
std::cout<< std::resetiosflags(std::ios_base::basefield);
std::cout<<std::setiosflags( std::ios_base::oct)<<l_iTemp<<std::endl;
//output: 57
std::cout<< std::resetiosflags(std::ios_base::basefield);
std::cout<<std::setiosflags( std::ios_base::hex)<<l_iTemp<<std::endl;
//output: 2f
std::cout<<std::setiosflags( std::ios_base::uppercase)<<l_iTemp<<std::endl;
//output 2F
std::cout<<std::setfill('0')<<std::setw(12);
std::cout<<std::resetiosflags(std::ios_base::uppercase);
std::cout<<std::setiosflags( std::ios_base::right)<<l_iTemp<<std::endl;
//output: 00000000002f
std::cout<<std::resetiosflags(std::ios_base::basefield|std::ios_base::adjustfield);
std::cout<<std::setfill('.')<<std::setw(10);
std::cout<<std::setiosflags( std::ios_base::left)<<l_iTemp<<std::endl;
//output: 47........
std::cout<<std::resetiosflags(std::ios_base::adjustfield)<<std::setfill('#');
std::cout<<std::setiosflags(std::ios_base::internal|std::ios_base::showpos);
std::cout<<std::setw(10)<<l_iTemp<<std::endl;
//output +#######47
https://riptutorial.com/es/home 747
Lea std :: iomanip en línea: https://riptutorial.com/es/cplusplus/topic/6936/std----iomanip
https://riptutorial.com/es/home 748
Capítulo 128: std :: map
Observaciones
• Para usar cualquiera de std::map o std::multimap debe incluir el archivo de encabezado <map>
.
• La diferencia básica entre std::map y std::multimap es que std::map one no permite valores
duplicados para la misma clave que std::multimap .
• size() funciones size() y empty() tienen una complejidad de tiempo Θ (1), el número de
nodos se almacena en caché para evitar recorrer el árbol cada vez que se llama a estas
funciones.
Examples
Elementos de acceso
ranking["stackoverflow"]=2;
ranking["docs-beta"]=1;
En un std::map , se puede acceder a los elementos directamente dando la clave como un índice:
Tenga en cuenta que usar el operator[] en el mapa insertará un nuevo valor con la clave
https://riptutorial.com/es/home 749
consultada en el mapa. Esto significa que no puede usarlo en un const std::map , incluso si la
clave ya está almacenada en el mapa. Para evitar esta inserción, verifique si el elemento existe
(por ejemplo, utilizando find() ) o use at() como se describe a continuación.
C ++ 11
Tenga at() cuenta que at() lanzará una excepción std::out_of_range si el contenedor no contiene
el elemento solicitado.
C ++ 11
https://riptutorial.com/es/home 750
std::map < int, std::string > mp { std::make_pair(2, "stackoverflow"),
std::make_pair(1, "docs-beta"),
std::make_pair(2, "stackexchange") };
// 1 docs-beta
// 2 stackoverflow
Borrando elementos
std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} };
mmp.clear(); //empty multimap
std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} };
// {1, 2}, {3, 4}, {3, 4}, {6, 5}, {6, 7}, {8, 9}
auto it = mmp.begin();
std::advance(it,3); // moved cursor on first {6, 5}
mmp.erase(it); // {1, 2}, {3, 4}, {3, 4}, {6, 7}, {8, 9}
std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} };
// {1, 2}, {3, 4}, {3, 4}, {6, 5}, {6, 7}, {8, 9}
auto it = mmp.begin();
auto it2 = it;
it++; //moved first cursor on first {3, 4}
std::advance(it2,3); //moved second cursor on first {6, 5}
mmp.erase(it,it2); // {1, 2}, {6, 5}, {6, 7}, {8, 9}
https://riptutorial.com/es/home 751
Eliminando todos los elementos que tengan un valor proporcionado como clave:
std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} };
// {1, 2}, {3, 4}, {3, 4}, {6, 5}, {6, 7}, {8, 9}
mmp.erase(6); // {1, 2}, {3, 4}, {3, 4}, {8, 9}
std::map<int,int> m;
auto it = m.begin();
while (it != m.end())
{
if (pred(*it))
it = m.erase(it);
else
++it;
}
Insertando elementos
fruits_count.insert({"grapes", 20});
fruits_count.insert(make_pair("orange", 30));
fruits_count.insert(pair<std::string, size_t>("banana", 40));
fruits_count.insert(map<std::string, size_t>::value_type("cherry", 50));
https://riptutorial.com/es/home 752
fruits_count["apple"] = 10;
Aunque es más simple, evita que el usuario verifique si el elemento ya existe. Si falta un
elemento, std::map::operator[] crea implícitamente, inicializándolo con el constructor
predeterminado antes de sobrescribirlo con el valor suministrado.
• insert()se puede usar para agregar varios elementos a la vez usando una lista de pares.
Esta versión de insert () devuelve void:
• insert()también se puede usar para agregar elementos mediante el uso de iteradores que
denotan el comienzo y el final de los valores de value_type valor:
std::map< std::string, size_t > fruit_list{ {"lemon", 0}, {"olive", 0}, {"plum", 0}};
fruits_count.insert(fruit_list.begin(), fruit_list.end());
Ejemplo:
La complejidad del tiempo para una operación de inserción es O (log n) porque std::map se
implementa como árboles.
C ++ 11
Si sabemos dónde se insertará el nuevo elemento, entonces podemos usar emplace_hint() para
especificar una hint iterador. Si el nuevo elemento se puede insertar justo antes de la hint ,
entonces la inserción se puede hacer en un tiempo constante. De lo contrario, se comporta de la
misma manera que emplace() :
https://riptutorial.com/es/home 753
Iterando sobre std :: map o std :: multimap
std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} };
//Forward iterator for loop: it would loop through first element to last element
//it will be a std::map< int, int >::iterator
for (auto it = mmp.begin(); it != mmp.end(); ++it)
std::cout<< it->first <<":"<< it->second << std::endl; //Do something with iterator
//Backward iterator for loop: it would loop through last element to first element
//it will be a std::map< int, int >::reverse_iterator
for (auto it = mmp.rbegin(); it != mmp.rend(); ++it)
std::cout<< it->first <<" "<< it->second << std::endl; //Do something with iterator
• Para obtener el iterador de la primera aparición de una clave, se puede usar la función
find() . Devuelve end() si la clave no existe.
std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} };
auto it = mmp.find(6);
if(it!=mmp.end())
std::cout << it->first << ", " << it->second << std::endl; //prints: 6, 5
else
std::cout << "Value does not exist!" << std::endl;
it = mmp.find(66);
if(it!=mmp.end())
std::cout << it->first << ", " << it->second << std::endl;
else
std::cout << "Value does not exist!" << std::endl; // This line would be executed.
std::map< int , int > mp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} };
if(mp.count(3) > 0) // 3 exists as a key in map
std::cout << "The key exists!" << std::endl; // This line would be executed.
else
https://riptutorial.com/es/home 754
std::cout << "The key does not exist!" << std::endl;
• En el caso de std::multimap , podría haber varios elementos que tengan la misma clave.
Para obtener este rango, se utiliza la función equal_range() que devuelve std::pair con un
límite inferior (inclusivo) del iterador y un límite superior (exclusivo) respectivamente. Si la
clave no existe, ambos iteradores apuntarían a end() .
El contenedor std::map tiene una función miembro empty() , que devuelve true o false ,
dependiendo de si el mapa está vacío o no. El size() función miembro size() devuelve el número
de elementos almacenados en un contenedor std::map :
Tipos de mapas
Mapa regular
Un mapa es un contenedor asociativo, que contiene pares clave-valor.
#include <string>
#include <map>
std::map<std::string, size_t> fruits_count;
La clave actúa como un índice en el mapa. Cada clave debe ser única y debe ser ordenada.
• Si necesita elementos múltiples con la misma clave, considere usar el multimap (que se
explica a continuación)
https://riptutorial.com/es/home 755
• Si su tipo de valor no especifica ningún pedido, o si desea anular el orden predeterminado,
puede proporcionar uno:
#include <string>
#include <map>
#include <cstring>
struct StrLess {
bool operator()(const std::string& a, const std::string& b) {
return strncmp(a.c_str(), b.c_str(), 8)<0;
//compare only up to 8 first characters
}
}
std::map<std::string, size_t, StrLess> fruits_count2;
Si el comparador StrLess devuelve false para dos claves, se consideran iguales incluso si
sus contenidos reales difieren.
Multi-Mapa
Multimap permite que múltiples pares clave-valor con la misma clave se almacenen en el mapa.
De lo contrario, su interfaz y creación es muy similar al mapa regular.
#include <string>
#include <map>
std::multimap<std::string, size_t> fruits_count;
std::multimap<std::string, size_t, StrLess> fruits_count2;
#include <string>
#include <unordered_map>
std::unordered_map<std::string, size_t> fruits_count;
Los mapas desordenados suelen ser más rápidos, pero los elementos no se almacenan en
ningún orden predecible. Por ejemplo, iterar sobre todos los elementos en un unordered_map da los
elementos en un orden aparentemente aleatorio.
Creando std :: map con tipos definidos por el usuario como clave
Para poder utilizar una clase como la clave en un mapa, todo lo que se requiere de la clave es
que sea copiable y assignable . El orden dentro del mapa se define por el tercer argumento de la
plantilla (y el argumento del constructor, si se usa). Por defecto, este es std::less<KeyType> , que
por defecto es el operador < , pero no hay ningún requisito para usar los valores por defecto.
Simplemente escriba un operador de comparación (preferiblemente como un objeto funcional):
https://riptutorial.com/es/home 756
struct CmpMyType
{
bool operator()( MyType const& lhs, MyType const& rhs ) const
{
// ...
}
};
Dos objetos x e y son equivalentes si f (x, y) y f (y, x) son falsos. Tenga en cuenta que
un objeto es siempre (por la invariante irreflexividad) equivalente a sí mismo.
En términos de C ++, esto significa que si tiene dos objetos de un tipo determinado, debe
devolver los siguientes valores en comparación con el operador <.
X a;
X b;
La forma en que defina el equivalente / menor depende totalmente del tipo de su objeto.
https://riptutorial.com/es/home 757
Capítulo 129: std :: opcional
Examples
Introducción
Los opcionales (también conocidos como tipos Maybe) se utilizan para representar un tipo cuyo
contenido puede o no estar presente. Se implementan en C ++ 17 como la clase std::optional .
Por ejemplo, un objeto de tipo std::optional<int> puede contener algún valor de tipo int , o puede
que no contenga ningún valor.
Los opcionales se utilizan comúnmente para representar un valor que puede no existir o como un
tipo de retorno de una función que puede fallar en devolver un resultado significativo.
Opcional vs puntero
En algunos casos, podemos proporcionar un puntero a un objeto existente o nullptr para indicar
un error. Pero esto se limita a aquellos casos en los que ya existen objetos: optional , como tipo
de valor, también se puede usar para devolver nuevos objetos sin tener que recurrir a la
asignación de memoria.
Opcional vs Sentinel
Un lenguaje común es usar un valor especial para indicar que el valor no tiene sentido. Esto
puede ser 0 o -1 para tipos integrales, o nullptr para punteros. Sin embargo, esto reduce el
espacio de valores válidos (no puede diferenciar entre un 0 válido y un 0 sin sentido) y muchos
tipos no tienen una opción natural para el valor centinela.
Otro modismo común es proporcionar un par, donde uno de los elementos es un bool indica si el
valor es significativo o no.
Esto se basa en que el tipo de valor es constructible por defecto en el caso de error, que no es
posible para algunos tipos y es posible pero no deseable para otros. Un optional<T> , en el caso
de error, no necesita construir nada.
Antes de C ++ 17, tener punteros con un valor de nullptr representaba comúnmente la ausencia
https://riptutorial.com/es/home 758
de un valor. Esta es una buena solución para objetos grandes que han sido asignados
dinámicamente y ya están gestionados por punteros. Sin embargo, esta solución no funciona bien
para tipos pequeños o primitivos, como int , que rara vez se asignan o administran
dinámicamente por punteros. std::optional proporciona una solución viable a este problema
común.
En este ejemplo, se define struct Person . Es posible que una persona tenga una mascota, pero
no es necesario. Por lo tanto, el miembro pet de Person se declara con un envoltorio std::optional
.
#include <iostream>
#include <optional>
#include <string>
struct Animal {
std::string name;
};
struct Person {
std::string name;
std::optional<Animal> pet;
};
int main() {
Person person;
person.name = "John";
if (person.pet) {
std::cout << person.name << "'s pet's name is " <<
person.pet->name << std::endl;
}
else {
std::cout << person.name << " is alone." << std::endl;
}
}
https://riptutorial.com/es/home 759
En este ejemplo, John recibe dos mascotas, Fluffy y Furball. Luego se llama a la función
Person::pet_with_name() para recuperar los bigotes de la mascota de John. Como John no tiene
una mascota llamada Whiskers, la función falla y std::nullopt se devuelve en su lugar.
#include <iostream>
#include <optional>
#include <string>
#include <vector>
struct Animal {
std::string name;
};
struct Person {
std::string name;
std::vector<Animal> pets;
int main() {
Person john;
john.name = "John";
Animal fluffy;
fluffy.name = "Fluffy";
john.pets.push_back(fluffy);
Animal furball;
furball.name = "Furball";
john.pets.push_back(furball);
Aquí devolvemos la fracción a/b , pero si no está definida (sería infinito), devolvemos el opcional
vacío.
https://riptutorial.com/es/home 760
Un caso más complejo:
find( some_range, 7 )busca en el contenedor o rango some_range para algo igual al número 7 .
find_if hace con un predicado.
Devuelve un opcional vacío si no fue encontrado, o un opcional que contiene un iterador para el
elemento si fue encontrado.
if (find( vec, 7 )) {
// code
}
o incluso
valor_o
Esto le permite tomar el opcional tal vez nulo y dar un comportamiento predeterminado cuando
realmente necesita un valor. Al hacerlo de esta manera, la decisión de "comportamiento
predeterminado" se puede retrasar hasta el punto en que se realice mejor y se necesite de
inmediato, en lugar de generar algún valor predeterminado en las entrañas de algún motor.
https://riptutorial.com/es/home 761
Capítulo 130: std :: par
Examples
Creando un par y accediendo a los elementos.
El par nos permite tratar dos objetos como un solo objeto. Los pares se pueden construir
fácilmente con la ayuda de la función de plantilla std::make_pair .
Una forma alternativa es crear un par y asignar sus elementos ( first y second ) más tarde.
#include <iostream>
#include <utility>
int main()
{
std::pair<int,int> p = std::make_pair(1,2); //Creating the pair
std::cout << p.first << " " << p.second << std::endl; //Accessing the elements
//We can also create a pair and assign the elements later
std::pair<int,int> p1;
p1.first = 3;
p1.second = 4;
std::cout << p1.first << " " << p1.second << std::endl;
return 0;
}
Comparar operadores
• operator== comprueba si ambos elementos en el par de lhs y rhs son iguales. El valor de
retorno es true si ambos lhs.first == rhs.first Y lhs.second == rhs.second , de lo contrario
false
if (p1 == p2)
std::cout << "equals";
else
std::cout << "not equal"//statement will show this, because they are not identical
• operator!= prueba si algún elemento en el par de lhs y rhs no es igual. El valor de retorno es
https://riptutorial.com/es/home 762
true si lhs.first != rhs.first O lhs.second != rhs.second , de lo contrario, devuelve false .
Otro ejemplo con contenedores de pares. Utiliza el operator< porque necesita ordenar el
contenedor.
#include <iostream>
#include <utility>
#include <vector>
#include <algorithm>
#include <string>
int main()
{
std::vector<std::pair<int, std::string>> v = { {2, "baz"},
{2, "bar"},
{1, "foo"} };
std::sort(v.begin(), v.end());
for(const auto& p: v) {
std::cout << "(" << p.first << "," << p.second << ") ";
//output: (1,foo) (2,bar) (2,baz)
}
}
https://riptutorial.com/es/home 763
Capítulo 131: std :: set y std :: multiset
Introducción
setes un tipo de contenedor cuyos elementos están ordenados y son únicos. multiset es similar,
pero, en el caso de multiset , múltiples elementos pueden tener el mismo valor.
Observaciones
Se han usado diferentes estilos de C ++ en esos ejemplos. Tenga cuidado de que si está
utilizando un compilador C ++ 98; Es posible que parte de este código no sea utilizable.
Examples
Insertando valores en un conjunto
• Primero, una simple inserción del valor. Este método devuelve un par que permite a la
persona que llama verificar si realmente se produjo la inserción.
• Segundo, una inserción dando una pista de donde se insertará el valor. El objetivo es
optimizar el tiempo de inserción en tal caso, pero saber dónde debe insertarse un valor no
es el caso común. Ten cuidado en ese caso; La forma de dar una pista difiere con las
versiones del compilador .
• Finalmente, puede insertar un rango de valores dando un puntero inicial y uno final. El inicio
se incluirá en la inserción, el final se excluye.
#include <iostream>
#include <set>
int main ()
{
std::set<int> sut;
std::set<int>::iterator it;
std::pair<std::set<int>::iterator,bool> ret;
// Basic insert
sut.insert(7);
sut.insert(5);
sut.insert(12);
ret = sut.insert(23);
if (ret.second==true)
std::cout << "# 23 has been inserted!" << std::endl;
ret = sut.insert(23); // since it's a set and 23 is already present in it, this insert
should fail
if (ret.second==false)
std::cout << "# 23 already present in set!" << std::endl;
https://riptutorial.com/es/home 764
// Insert with hint for optimization
it = sut.end();
// This case is optimized for C++11 and above
// For earlier version, point to the element preceding your insertion
sut.insert(it, 30);
std::cout << std::endl << "Set under test contains:" << std::endl;
for (it = sut.begin(); it != sut.end(); ++it)
{
std::cout << *it << std::endl;
}
return 0;
}
La salida será:
12
20
23
30
45
Todos los métodos de inserción de conjuntos también se aplican a multisets. Sin embargo, existe
otra posibilidad, que es proporcionar una initial__list:
auto il = { 7, 5, 12 };
std::multiset<int> msut;
https://riptutorial.com/es/home 765
msut.insert(il);
Imaginemos que estamos almacenando valores de cadena en un conjunto, pero sabemos que
esas cadenas contienen solo valores numéricos. Por defecto, la clasificación será una
comparación de cadena lexicográfica, por lo que el orden no coincidirá con la clasificación
numérica. Si desea aplicar una clasificación equivalente a la que tendría con los valores int ,
necesita un functor para sobrecargar el método de comparación:
#include <iostream>
#include <set>
#include <stdlib.h>
int main ()
{
std::set<std::string> sut({"1", "2", "5", "23", "6", "290"});
std::cout << std::endl << "### Custom sort on set :" << std::endl;
for (auto &&data : sut_custom)
std::cout << data << std::endl;
auto compare_via_lambda = [](auto &&lhs, auto &&rhs){ return lhs > rhs; };
using set_via_lambda = std::set<std::string, decltype(compare_via_lambda)>;
set_via_lambda sut_reverse_via_lambda({"1", "2", "5", "23", "6", "290"},
compare_via_lambda);
std::cout << std::endl << "### Lambda sort on set :" << std::endl;
for (auto &&data : sut_reverse_via_lambda)
std::cout << data << std::endl;
return 0;
}
La salida será:
https://riptutorial.com/es/home 766
### Default sort on std::set<std::string> :
1
2
23
290
5
6
### Custom sort on set :
1
2
5
6
23
290
Orden predeterminado
Esto usará el operador de comparación de la clave (primer argumento de la plantilla). A menudo,
la clave ya proporcionará un buen valor predeterminado para la función std::less<T> . A menos
que esta función sea especializada, utiliza el operator< del objeto. Esto es especialmente útil
cuando otro código también intenta usar algunos pedidos, ya que esto permite la coherencia en
toda la base del código.
Escribir el código de esta manera reducirá el esfuerzo de actualizar su código cuando la clave
cambie en API, como: una clase que contiene 2 miembros que cambia a una clase que contiene 3
miembros. Al actualizar el operator< en la clase, todas las ocurrencias se actualizarán.
Orden personalizado
La adición de una ordenación personalizada a través de un objeto con un operador de
comparación se usa a menudo cuando la comparación predeterminada no cumple. En el ejemplo
anterior, esto se debe a que las cadenas se refieren a números enteros. En otros casos, a
menudo se usa cuando se desean comparar (inteligentes) los punteros en función del objeto al
que hacen referencia o porque se necesitan diferentes restricciones para comparar (ejemplo:
comparar std::pair por el valor de first ).
Al crear un operador de comparación, esto debería ser una clasificación estable. Si el resultado
https://riptutorial.com/es/home 767
del operador de comparación cambia después de la inserción, tendrá un comportamiento
indefinido. Como buena práctica, su operador de comparación solo debe usar los datos
constantes (miembros const, funciones const ...).
Como en el ejemplo anterior, a menudo encontrará clases sin miembros como operadores de
comparación. Esto da como resultado constructores por defecto y constructores de copia. El
constructor predeterminado le permite omitir la instancia en el momento de la construcción y se
requiere el constructor de copia ya que el conjunto toma una copia del operador de comparación.
Tipo lambda
Las Lambdas son una forma más corta de escribir objetos de función. Esto permite escribir el
operador de comparación en menos líneas, lo que hace que el código general sea más legible.
La desventaja del uso de lambdas es que cada lambda obtiene un tipo específico en el momento
de la compilación, por lo que decltype(lambda) será diferente para cada compilación de la misma
unidad de compilación (archivo cpp) que sobre varias unidades de compilación (cuando se incluye
a través del archivo de cabecera ). Por esta razón, se recomienda usar objetos de función como
operador de comparación cuando se usa dentro de los archivos de encabezado.
Esta construcción se encuentra a menudo cuando se usa un std::set dentro del alcance local de
una función, mientras que el objeto de la función se prefiere cuando se usa como argumentos de
la función o miembros de la clase.
Para obtener el iterador de la primera aparición de una clave, se puede usar la función find() .
Devuelve end() si la clave no existe.
std::set<int> sut;
sut.insert(10);
sut.insert(15);
sut.insert(22);
sut.insert(3); // contains 3, 10, 15, 22
https://riptutorial.com/es/home 768
std::multiset<int> msut;
sut.insert(10);
sut.insert(15);
sut.insert(22);
sut.insert(15);
sut.insert(3); // contains 3, 10, 15, 15, 22
Otra forma es usar la función count() , que cuenta cuántos valores correspondientes se han
encontrado en el set / set multiset (en el caso de un set , el valor de retorno puede ser solo 0 o 1).
Usando los mismos valores que arriba, tendremos:
En el caso de std::multiset , puede haber varios elementos que tengan el mismo valor. Para
obtener este rango, se puede usar la función equal_range() . Devuelve std::pair con iterador límite
inferior (inclusivo) y límite superior (exclusivo) respectivamente. Si la clave no existe, ambos
iteradores apuntarían al valor superior más cercano (basado en el método de comparación usado
para ordenar el multiset dado).
El método más obvio, si solo desea restablecer su conjunto / conjunto múltiple a uno vacío, es
usar clear :
std::set<int> sut;
sut.insert(10);
sut.insert(15);
sut.insert(22);
sut.insert(3);
sut.clear(); //size of sut is 0
Luego se puede utilizar el método de erase . Ofrece algunas posibilidades que parecen un tanto
equivalentes a la inserción:
std::set<int> sut;
std::set<int>::iterator it;
sut.insert(10);
sut.insert(15);
https://riptutorial.com/es/home 769
sut.insert(22);
sut.insert(3);
sut.insert(30);
sut.insert(33);
sut.insert(45);
// Basic deletion
sut.erase(3);
// Using iterator
it = sut.find(22);
sut.erase(it);
std::cout << std::endl << "Set under test contains:" << std::endl;
for (it = sut.begin(); it != sut.end(); ++it)
{
std::cout << *it << std::endl;
}
La salida será:
10
15
30
Todos esos métodos también se aplican a multiset . Tenga en cuenta que si solicita eliminar un
elemento de un multiset y está presente varias veces, se eliminarán todos los valores
equivalentes .
https://riptutorial.com/es/home 770
Capítulo 132: std :: string
Introducción
Las cadenas son objetos que representan secuencias de caracteres. El estándar string clase
proporciona una alternativa sencilla, segura y versátil para la utilización de matrices explícitas de
char s cuando se trata de texto y otras secuencias de caracteres. La clase de string C ++ es parte
del std nombres estándar y se estandarizó en 1998.
Sintaxis
• // declaración de cadena vacía
std :: string s;
std :: string s2 (s1.begin (), s1.begin () + 5); // Copia los primeros 5 caracteres de s1 en s2
https://riptutorial.com/es/home 771
Observaciones
Antes de usar std::string , debe incluir la string del encabezado, ya que incluye funciones /
operadores / sobrecargas que otros encabezados (por ejemplo, iostream ) no incluyen.
std::string oops(nullptr);
std::cout << oops << "\n";
El comportamiento del operator[] es un poco más complicado, en todos los casos tiene un
comportamiento indefinido si index > size() , pero cuando index == size() :
C ++ 11
C ++ 11
1. Se CharT() una referencia a un carácter con valor CharT() (el carácter nulo ).
2. Modificar esta referencia es un comportamiento indefinido .
Como C ++ 14, en lugar de usar "foo" , se recomienda usar "foo"s , ya que s es un sufijo literal
definido por el usuario , que convierte el const char* "foo" en std::string "foo" .
Examples
Terrible
Usa std::string::substr para dividir una cadena. Hay dos variantes de esta función miembro.
La primera toma una posición inicial desde la cual debe comenzar la subcadena devuelta. La
posición inicial debe ser válida en el rango (0, str.length()] :
El segundo toma una posición inicial y una longitud total de la nueva subcadena.
Independientemente de la longitud , la subcadena nunca pasará del final de la cadena de origen:
https://riptutorial.com/es/home 772
std::string str = "Hello foo, bar and world!";
std::string newstr = str.substr(15, 3); // "and"
Tenga en cuenta que también puede llamar a substr sin argumentos, en este caso se devuelve
una copia exacta de la cadena
Reemplazo de cuerdas
//Define string
std::string str = "Hello foo, bar and world!";
std::string alternate = "Hello foobar";
//1)
str.replace(6, 3, "bar"); //"Hello bar, bar and world!"
//2)
str.replace(str.begin() + 6, str.end(), "nobody!"); //"Hello nobody!"
//3)
str.replace(19, 5, alternate, 6, 6); //"Hello foo, bar and foobar!"
C ++ 14
//4)
str.replace(19, 5, alternate, 6); //"Hello foo, bar and foobar!"
//5)
str.replace(str.begin(), str.begin() + 5, str.begin() + 6, str.begin() + 9);
//"foo foo, bar and world!"
//6)
str.replace(0, 5, 3, 'z'); //"zzz foo, bar and world!"
//7)
str.replace(str.begin() + 6, str.begin() + 9, 3, 'x'); //"Hello xxx, bar and world!"
C ++ 11
//8)
str.replace(str.begin(), str.begin() + 5, { 'x', 'y', 'z' }); //"xyz foo, bar and world!"
https://riptutorial.com/es/home 773
Reemplazar las ocurrencias de una cadena
con otra cadena
Reemplace solo la primera vez que se replace con with en str :
Concatenación
Usando el operador += :
https://riptutorial.com/es/home 774
También puede utilizar push_back() para hacer retroceder individuo char s:
Accediendo a un personaje
Hay varias formas de extraer caracteres de una std::string y cada una es sutilmente diferente.
operador [] (n)
std::string::operator[]no está verificada por límites y no lanza una excepción. La persona que
llama es responsable de afirmar que el índice está dentro del rango de la cadena:
en (n)
C ++ 11
Nota: Ambos ejemplos darán como resultado un comportamiento indefinido si la cadena está
vacía.
frente()
https://riptutorial.com/es/home 775
atrás()
Tokenizar
// String to tokenize
std::string str{ "The quick brown fox" };
// Vector to store tokens
vector<std::string> tokens;
Ejemplo vivo
// String to tokenize
const std::string str("The quick \tbrown \nfox");
std::istringstream is(str);
// Vector to store tokens
const std::vector<std::string> tokens = std::vector<std::string>(
std::istream_iterator<std::string>(is),
https://riptutorial.com/es/home 776
std::istream_iterator<std::string>());
Ejemplo vivo
C ++ 11
// String to tokenize
const std::string str{ "The ,qu\\,ick ,\tbrown, fox" };
const std::regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
// Vector to store tokens
const std::vector<std::string> tokens{
std::sregex_token_iterator(str.begin(), str.end(), re, 1),
std::sregex_token_iterator()
};
Ejemplo vivo
Para obtener acceso const char* a los datos de un std::string , puede usar la función miembro
c_str() del string. Tenga en cuenta que el puntero solo es válido mientras el objeto std::string
esté dentro del alcance y permanezca sin cambios, lo que significa que solo se puede llamar a los
métodos const en el objeto.
C ++ 17
La función miembro de data() se puede usar para obtener un char* modificable, que se puede
usar para manipular los datos del objeto std::string .
C ++ 11
También se puede obtener un char* modificable tomando la dirección del primer carácter: &s[0] .
En C ++ 11, se garantiza que esto produce una cadena bien formada, terminada en nulo. Tenga
en cuenta que &s[0] está bien formado incluso si s está vacío, mientras que &s.front() no está
definido si s está vacío.
C ++ 11
// Copy the contents of str to untie lifetime from the std::string object
https://riptutorial.com/es/home 777
std::unique_ptr<char []> cstr = std::make_unique<char[]>(str.size() + 1);
// delete[] cstr_unsafe;
std::cout << cstr.get();
Para encontrar un carácter u otra cadena, puede usar std::string::find . Devuelve la posición del
primer carácter de la primera partida. Si no se encontraron coincidencias, la función devuelve
std::string::npos
if (it != std::string::npos)
std::cout << "Found at position: " << it << '\n';
else
std::cout << "Not found!\n";
Encontrado en la posición: 21
Las oportunidades de búsqueda se amplían aún más por las siguientes funciones:
Estas funciones pueden permitirle buscar caracteres desde el final de la cadena, así como
encontrar el caso negativo (es decir, los caracteres que no están en la cadena). Aquí hay un
ejemplo:
Encontrado en la posición: 6
Nota: tenga en cuenta que las funciones anteriores no buscan subcadenas, sino caracteres
contenidos en la cadena de búsqueda. En este caso, la última aparición de 'g' se encontró en la
posición 6 (los otros caracteres no se encontraron).
C ++ 11
https://riptutorial.com/es/home 778
Recortar una secuencia o cadena significa eliminar todos los elementos (o caracteres) iniciales y
finales que coinciden con un determinado predicado. Primero recortamos los elementos finales,
porque no implica mover ningún elemento, y luego recortamos los elementos iniciales. Tenga en
cuenta que las generalizaciones siguientes funcionan para todos los tipos de std::basic_string
(por ejemplo, std::string y std::wstring ), y accidentalmente también para contenedores de
secuencias (por ejemplo, std::vector y std::list ).
Recortar los elementos finales implica encontrar el último elemento que no coincide con el
predicado y borrar de allí en adelante:
Recortar los elementos iniciales implica encontrar el primer elemento que no coincide con el
predicado y borrar hasta allí:
Para especializar lo anterior para recortar espacios en blanco en una std::string std::isspace()
podemos usar la función std::isspace() como predicado:
https://riptutorial.com/es/home 779
Si desea crear una nueva secuencia que sea una copia recortada, puede usar una función
separada:
Comparacion lexicografica
Dos std::string s se pueden comparar lexicográficamente usando los operadores == != , < , <= , >
Y >= :
• operador == :
• operador != :
Busca el primer par de caracteres diferentes, los compara y luego devuelve el resultado
booleano.
Busca el primer par de caracteres diferentes, los compara y luego devuelve el resultado
booleano.
Nota: el término par de caracteres significa los caracteres correspondientes en ambas cadenas
de las mismas posiciones. Para una mejor comprensión, si dos cadenas de ejemplo son str1 y
str2
https://riptutorial.com/es/home 780
, y sus longitudes son n y m respectivamente, entonces los pares de caracteres de ambas cadenas
significan cada par str1[i] y str2[i] donde i = 0, 1, 2,. .., max (n, m) . Si por alguna donde no
existe i el carácter correspondiente, es decir, cuando i es mayor o igual a n o m , sería considerado
como el valor más bajo.
#include <string>
#include <codecvt>
#include <locale>
std::string input_str = "this is a -string-, which is a sequence based on the -char- type.";
std::wstring input_wstr = L"this is a -wide- string, which is based on the -wchar_t- type.";
// conversion
std::wstring str_turned_to_wstr =
std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(input_str);
std::string wstr_turned_to_str =
std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(input_wstr);
Para mejorar la facilidad de uso y / o la legibilidad, puede definir funciones para realizar la
conversión:
#include <string>
#include <codecvt>
https://riptutorial.com/es/home 781
#include <locale>
Uso de la muestra:
C ++ 17
void foo(const char* s, size_t len); // pre-C++17, two arguments, have to pass them
// both everywhere
void foo(const char* s); // pre-C++17, single argument, but need to call
// strlen()
https://riptutorial.com/es/home 782
Todos estos pueden ser reemplazados por:
Ofrece un subconjunto útil de la funcionalidad que hace std::string , aunque algunas de las
funciones se comportan de manera diferente:
//Bad way - 'string::substr' returns a new string (expensive if the string is long)
std::cout << str.substr(15, 10) << '\n';
C ++ 11
std::stringadmite iteradores, por lo que puede utilizar un bucle basado en rangos para recorrer
cada carácter:
Una std::string contiene un número se puede convertir en un tipo entero, o un tipo de punto
flotante, usando funciones de conversión.
Tenga en cuenta que todas estas funciones dejan de analizar la cadena de entrada tan pronto
como encuentran un carácter no numérico, por lo que "123abc" se convertirá en 123 .
https://riptutorial.com/es/home 783
enteros o de punto flotante:
C ++ 11
Sin embargo, se desaconseja el uso de estas funciones porque devuelven 0 si no pueden analizar
la cadena. Esto es malo porque 0 también podría ser un resultado válido, si, por ejemplo, la
cadena de entrada era "0", por lo que es imposible determinar si la conversión realmente falló.
C ++ 11
https://riptutorial.com/es/home 784
#include <iostream>
#include <codecvt>
#include <locale>
#include <string>
using namespace std;
int main() {
// converts between wstring and utf8 string
wstring_convert<codecvt_utf8_utf16<wchar_t>> wchar_to_utf8;
// converts between u16string and utf8 string
wstring_convert<codecvt_utf8_utf16<char16_t>, char16_t> utf16_to_utf8;
return 0;
}
Tenga en cuenta que Visual Studio 2015 proporciona soporte para estas conversiones, pero un
error en la implementación de su biblioteca requiere el uso de una plantilla diferente para
wstring_convert cuando se trata de char16_t :
C ++ 14
En C ++ 14, esto se hace fácilmente mediante std::mismatch que devuelve el primer par no
coincidente de dos rangos:
https://riptutorial.com/es/home 785
std::string prefix = "foo";
std::string string = "foobar";
Tenga en cuenta que existía una versión con rango y medio de mismatch() anterior a C ++ 14,
pero esto no es seguro en el caso de que la segunda cadena sea la más corta de las dos.
C ++ 14
Todavía podemos usar la versión de rango y medio de std::mismatch() , pero primero debemos
verificar que la primera cadena sea tan grande como la segunda:
C ++ 17
Con std::string_view , podemos escribir la comparación directa que queremos sin tener que
preocuparnos por la sobrecarga de asignación o hacer copias:
#include <sstream>
int main()
{
int val = 4;
std::ostringstream str;
str << val;
std::string converted = str.str();
return 0;
}
template<class T>
std::string toString(const T& x)
{
https://riptutorial.com/es/home 786
std::ostringstream ss;
ss << x;
return ss.str();
}
Las clases definidas por el usuario pueden implementar el operador de inserción de flujo si lo
desea:
C ++ 11
https://riptutorial.com/es/home 787
Capítulo 133: std :: variante
Observaciones
La variante es un reemplazo para el uso de la union cruda. Es de tipo seguro y sabe de qué tipo
es, y construye y destruye cuidadosamente los objetos dentro de él cuando debería.
Casi nunca está vacío: solo en los casos de esquina donde se reemplaza el contenido y no se
puede retroceder de manera segura, se queda en un estado vacío.
Usar std::get y std::get_if suele ser una mala idea. La respuesta correcta suele ser std::visit ,
que le permite lidiar con todas las posibilidades allí mismo. if constexpr se puede usar if
constexpr dentro de la visit si necesita ramificar su comportamiento, en lugar de hacer una
secuencia de verificaciones de tiempo de ejecución que dupliquen lo que la visit hará de manera
más eficiente.
Examples
Basic std :: uso variante
Esto crea una variante (una unión etiquetada) que puede almacenar un int o una string .
var = "hello"s;
// Prints "hello\n":
visit( [](auto&& e) {
std::cout << e << '\n';
}, var );
https://riptutorial.com/es/home 788
devuelve nullptr si nullptr mal.
Las variantes no garantizan una asignación de memoria dinámica (aparte de la que se asigna por
sus tipos contenidos). Solo uno de los tipos en una variante se almacena allí, y en casos raros
(que involucran excepciones al asignar y no hay una forma segura de retroceder), la variante
puede quedar vacía.
Las variantes le permiten almacenar múltiples tipos de valores en una variable de manera segura
y eficiente. Básicamente son union inteligentes y seguros.
template<class F>
struct pseudo_method {
F f;
// enable C++17 class type deduction:
pseudo_method( F&& fin ):f(std::move(fin)) {}
esto crea un tipo que sobrecarga al operator->* con una Variant en el lado izquierdo.
struct A {
void print( std::ostream& os ) const {
os << "A";
https://riptutorial.com/es/home 789
}
};
struct B {
void print( std::ostream& os ) const {
os << "B";
}
};
(var->*print)(std::cout);
struct C {};
entonces:
La extensión de lo anterior permitiría que se detecten y utilicen las print función libre,
posiblemente con el uso de if constexpr dentro del pseudo-método de print .
struct A {};
struct B { B()=default; B(B const&)=default; B(int){}; };
struct C { C()=delete; C(int) {}; C(C const&)=default; };
struct D { D( std::initializer_list<int> ) {}; D(D const&)=default; D()=default; };
https://riptutorial.com/es/home 790
Capítulo 134: std :: vector
Introducción
Un vector es una matriz dinámica con almacenamiento manejado automáticamente. Se puede
acceder a los elementos de un vector con la misma eficacia que los de una matriz, con la ventaja
de que los vectores pueden cambiar dinámicamente de tamaño.
Observaciones
El uso de un std::vector requiere la inclusión del encabezado <vector> usando #include <vector> .
Los elementos en un std::vector se almacenan de forma contigua en la tienda libre. Cabe señalar
que cuando los vectores se anidan como en std::vector<std::vector<int> > , los elementos de
cada vector son contiguos, pero cada vector asigna su propio búfer subyacente en el almacén
libre.
Examples
Inicializando un std :: vector
C ++ 11
std::vector<int> v(v2);
std::vector<int> v = v2;
https://riptutorial.com/es/home 791
C ++ 11
Mueve la construcción (solo desde otro vector), que mueve los datos de v2 :
std::vector<int> v(std::move(v2));
std::vector<int> v = std::move(v2);
// from an array
int z[] = { 1, 2, 3, 4 };
std::vector<int> v(z, z + 3); // v becomes {1, 2, 3}
// from a list
std::list<int> list1{ 1, 2, 3 };
std::vector<int> v(list1.begin(), list1.end()); // v becomes {1, 2, 3}
C ++ 11
// from a list
std::list<int> list1{ 1, 2, 3 };
std::vector<int> v(std::make_move_iterator(list1.begin()),
std::make_move_iterator(list1.end()));
int z[] = { 1, 2, 3, 4 };
v.assign(z + 1, z + 4); // v becomes {2, 3, 4}
Insertando Elementos
struct Point {
double x, y;
Point(double x, double y) : x(x), y(y) {}
};
std::vector<Point> v;
Point p(10.0, 2.0);
v.push_back(p); // p is copied into the vector.
https://riptutorial.com/es/home 792
C ++ 11
std::vector<Point> v;
v.emplace_back(10.0, 2.0); // The arguments are passed to the constructor of the
// given type (here Point). The object is constructed
// in the vector, avoiding a copy.
Tenga en cuenta que std::vector no tiene una función miembro push_front() debido a razones de
rendimiento. Al agregar un elemento al principio, se mueven todos los elementos existentes en el
vector. Si desea insertar elementos con frecuencia al principio de su contenedor, puede usar
std::list o std::deque .
std::vector<int> v{ 1, 2, 3 };
v.insert(v.begin(), 9); // v now contains {9, 1, 2, 3}
C ++ 11
std::vector<int> v{ 1, 2, 3 };
v.emplace(v.begin()+1, 9); // v now contains {1, 9, 2, 3}
Use reserve() antes de insertar múltiples elementos si el tamaño del vector resultante se conoce
de antemano para evitar múltiples reasignaciones (consulte el tamaño y la capacidad del vector ):
std::vector<int> v;
v.reserve(100);
for(int i = 0; i < 100; ++i)
v.emplace_back(i);
https://riptutorial.com/es/home 793
Iterando Sobre std :: vector
Puedes iterar sobre un std::vector de varias maneras. Para cada una de las siguientes
secciones, v se define como sigue:
std::vector<int> v;
C ++ 11
https://riptutorial.com/es/home 794
// Using for_each algorithm
// Note: Using a lambda for clarity. But a function or functor will work
std::for_each(std::rbegin(v), std::rend(v), [](auto const& value) {
std::cout << value << "\n";
});
Aunque no hay una forma integrada de usar el rango para revertir iteración; Es relativamente
sencillo arreglar esto. El rango basado en los usos begin() y end() para obtener iteradores y, por
lo tanto, simular esto con un objeto de envoltura puede lograr los resultados que requerimos.
C ++ 14
template<class C>
struct ReverseRange {
C c; // could be a reference or a copy, if the original was a temporary
ReverseRange(C&& cin): c(std::forward<C>(cin)) {}
ReverseRange(ReverseRange&&)=default;
ReverseRange& operator=(ReverseRange&&)=delete;
auto begin() const {return std::rbegin(c);}
auto end() const {return std::rend(c);}
};
// C is meant to be deduced, and perfect forwarded into
template<class C>
ReverseRange<C> make_ReverseRange(C&& c) {return {std::forward<C>(c)};}
int main() {
std::vector<int> v { 1,2,3,4};
for(auto const& value: make_ReverseRange(v)) {
std::cout << value << "\n";
}
}
C ++ 11
// forward iteration
for (auto pos = v.cbegin(); pos != v.cend(); ++pos) {
// type of pos is vector<T>::const_iterator
https://riptutorial.com/es/home 795
// *pos = 5; // Compile error - can't write via const iterator
}
// reverse iteration
for (auto pos = v.crbegin(); pos != v.crend(); ++pos) {
// type of pos is vector<T>::const_iterator
// *pos = 5; // Compile error - can't write via const iterator
}
// expects Functor::operand()(T&)
for_each(v.begin(), v.end(), Functor());
C ++ 17
C ++ 14
Elementos de acceso
https://riptutorial.com/es/home 796
Acceso basado en índices:
Esto se puede hacer con el operador de subíndice [] o la función miembro at() .
[] y at() difieren en que [] no se garantiza que realice ninguna comprobación de límites, mientras
que at() hace. El acceso a los elementos donde index < 0 o index >= size es un comportamiento
indefinido para [] , mientras que at() lanza una excepción std::out_of_range .
Nota: Los ejemplos a continuación utilizan la inicialización del estilo C ++ 11 para mayor claridad,
pero los operadores se pueden usar con todas las versiones (a menos que estén marcados con C
++ 11).
C ++ 11
std::vector<int> v{ 1, 2, 3 };
// using []
int a = v[1]; // a is 2
v[1] = 4; // v now contains { 1, 4, 3 }
// using at()
int b = v.at(2); // b is 3
v.at(2) = 5; // v now contains { 1, 4, 5 }
int c = v.at(3); // throws std::out_of_range exception
Debido a que el método at() realiza la verificación de límites y puede lanzar excepciones, es más
lento que [] . Esto hace que [] código preferido donde la semántica de la operación garantice que
el índice está dentro de los límites. En cualquier caso, los accesos a elementos de vectores se
realizan en tiempo constante. Eso significa que acceder al primer elemento del vector tiene el
mismo costo (en el tiempo) de acceder al segundo elemento, al tercer elemento y así
sucesivamente.
Aquí sabemos que la variable de índice i siempre está dentro de los límites, por lo que sería una
pérdida de ciclos de CPU comprobar que i está dentro de los límites para cada llamada al
operator[] .
Las funciones de miembro front() y back() permiten un acceso de referencia fácil al primer y
último elemento del vector, respectivamente. Estas posiciones se utilizan con frecuencia y los
accesores especiales pueden ser más legibles que sus alternativas utilizando [] :
https://riptutorial.com/es/home 797
std::vector<int> v{ 4, 5, 6 }; // In pre-C++11 this is more verbose
Nota : es un comportamiento indefinido invocar front() o back() en un vector vacío. Debe verificar
que el contenedor no esté vacío utilizando la función miembro empty() (que verifica si el
contenedor está vacío) antes de llamar a front() o back() . A continuación se muestra un ejemplo
simple del uso de 'empty ()' para probar un vector vacío:
int main ()
{
std::vector<int> v;
int sum (0);
std::cout << "total: " << sum << '\n';//output the total to the user
return 0;
}
El ejemplo anterior crea un vector con una secuencia de números del 1 al 10. Luego saca los
elementos del vector hasta que el vector está vacío (usando 'vacío ()') para evitar un
comportamiento indefinido. Luego, la suma de los números en el vector se calcula y se muestra al
usuario.
C ++ 11
El método data() devuelve un puntero a la memoria sin formato utilizada por std::vector para
almacenar internamente sus elementos. Esto se usa con más frecuencia cuando se pasan los
datos vectoriales a un código heredado que espera una matriz de estilo C.
C ++ 11
Antes de C ++ 11, el método data() se puede simular llamando a front() y tomando la dirección
del valor devuelto:
https://riptutorial.com/es/home 798
std::vector<int> v(4);
int* ptr = &(v.front()); // or &v[0]
Esto funciona porque los vectores siempre tienen la garantía de almacenar sus elementos en
ubicaciones de memoria contiguas, asumiendo que el contenido del vector no anula al operator&
unario operator& . Si lo hace, tendrás que volver a implementar std::addressof en pre-C ++ 11.
También supone que el vector no está vacío.
Iteradores
Los iteradores se explican más detalladamente en el ejemplo "Iterando sobre std::vector " y el
artículo Iteradores . En resumen, actúan de manera similar a los punteros a los elementos del
vector:
C ++ 11
std::vector<int> v{ 4, 5, 6 };
auto it = v.begin();
int i = *it; // i is 4
++it;
i = *it; // i is 5
*it = 6; // v contains { 4, 6, 6 }
auto e = v.end(); // e points to the element after the end of v. It can be
// used to check whether an iterator reached the end of the vector:
++it;
it == v.end(); // false, it points to the element at position 2 (with value 6)
++it;
it == v.end(); // true
Es consistente con el estándar que los iteradores de std::vector<T> realidad son T* s, pero la
mayoría de las bibliotecas estándar no lo hacen. No hacer esto mejora los mensajes de error,
atrapa el código no portátil y se puede usar para instrumentar los iteradores con las
comprobaciones de depuración en las compilaciones no liberadas. Luego, en las compilaciones
de lanzamiento, la clase que rodea el puntero subyacente se optimiza.
Puede persistir una referencia o un puntero a un elemento de un vector para el acceso indirecto.
Estas referencias o punteros a elementos en el vector permanecen estables y el acceso
permanece definido a menos que agregue / elimine elementos en o antes del elemento en el
vector , o haga que cambie la capacidad del vector . Esta es la misma que la regla para invalidar
iteradores.
C ++ 11
std::vector<int> v{ 1, 2, 3 };
int* p = v.data() + 1; // p points to 2
v.insert(v.begin(), 0); // p is now invalid, accessing *p is a undefined behavior.
p = v.data() + 1; // p points to 1
v.reserve(10); // p is now invalid, accessing *p is a undefined behavior.
p = v.data() + 1; // p points to 1
https://riptutorial.com/es/home 799
v.erase(v.begin()); // p is now invalid, accessing *p is a undefined behavior.
Hay varias formas de usar un std::vector como una matriz de C (por ejemplo, para la
compatibilidad con las bibliotecas de C). Esto es posible porque los elementos en un vector se
almacenan de forma contigua.
C ++ 11
std::vector<int> v{ 1, 2, 3 };
int* p = v.data();
Antes de C ++ 11, tomaría la dirección del primer elemento del vector para obtener un puntero
equivalente, si el vector no está vacío, estos dos métodos son intercambiables:
Nota: Si el vector está vacío, v[0] y v.front() no están definidos y no se pueden usar.
Al almacenar la dirección base de los datos del vector, tenga en cuenta que muchas operaciones
(como push_back , resize , etc.) pueden cambiar la ubicación de la memoria de datos del vector, lo
que invalida los punteros de datos anteriores . Por ejemplo:
std::vector<int> v;
int* p = v.data();
v.resize(42); // internal memory location changed; value of p is now invalid
Los iteradores y los punteros que apuntan a un std::vector pueden volverse inválidos, pero solo
cuando se realizan ciertas operaciones. El uso de iteradores / punteros no válidos resultará en un
comportamiento indefinido.
• Cualquier operación de inserción que cambie la capacity del vector invalidará todos los
iteradores / punteros:
https://riptutorial.com/es/home 800
v.reserve(20); // Capacity is now at least 20.
int *p2 = &v[0];
v.push_back(4); // p2 is *not* invalidated, since the size of `v` is now 7.
v.insert(v.end(), 30, 9); // Inserts 30 elements at the end. The size exceeds the
// requested capacity of 20, so `p2` is (probably) invalidated.
int *p3 = &v[0];
v.reserve(v.capacity() + 20); // Capacity exceeded, thus `p3` is invalid.
C ++ 11
vector<int> v(5);
v.reserve(20); // Capacity is at least 20.
int *p1 = &v[0];
int *p2 = &v[3];
v.insert(v.begin() + 2, 5, 0); // `p2` is invalidated, but since the capacity
// did not change, `p1` remains valid.
int *p3 = &v[v.size() - 1];
v.push_back(10); // The capacity did not change, so `p3` and `p1` remain valid.
• Cualquier operación de eliminación invalidará los iteradores / punteros que apuntan a los
elementos eliminados y a cualquier elemento más allá de los elementos eliminados. Esto
incluye el iterador end :
vector<int> v(10);
int *p1 = &v[0];
int *p2 = &v[5];
v.erase(v.begin() + 3, v.end()); // `p2` is invalid, but `p1` remains valid.
• operator=(copiar, mover u otro) y clear() invalidarán todos los iteradores / punteros que
apuntan al vector.
Borrando elementos
https://riptutorial.com/es/home 801
std::vector<int> v{ 1, 2, 3 };
v.clear(); // v becomes an empty vector
Nota: Para un vector elimina un elemento que no es el último elemento, todos los elementos más
allá del elemento eliminado deben copiarse o moverse para llenar el espacio, consulte la nota a
continuación y std :: list .
Nota: Los métodos anteriores no cambian la capacidad del vector, solo el tamaño. Ver Tamaño y
Capacidad del Vector .
El método de erase , que elimina una gama de elementos, se utiliza a menudo como parte del
lenguaje de borrado-eliminar . Es decir, primero std::remove mueve algunos elementos al final
del vector, y luego erase cortes. Esta es una operación relativamente ineficiente para cualquier
índice menor que el último índice del vector porque todos los elementos después de los
segmentos borrados deben reubicarse en nuevas posiciones. Para aplicaciones críticas de
velocidad que requieren la eliminación eficiente de elementos arbitrarios en un contenedor, vea
std :: list .
https://riptutorial.com/es/home 802
Eliminar elementos por lambda, sin crear una
función de predicado adicional
C ++ 11
std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
v.erase(std::remove_if(v.begin(), v.end(),
[](auto& element){return element > 3;} ), v.end()
);
• Dado un iterador inverso it apunta a algún elemento, la base del método proporciona el
iterador regular (no inverso) que apunta al mismo elemento.
https://riptutorial.com/es/home 803
• vector::erase(iterator) borra el elemento apuntado por un iterador y devuelve un iterador al
elemento que siguió al elemento dado.
Eliminar todos los elementos usando v.clear() no libera memoria (la capacity() del vector
permanece sin cambios). Para reclamar espacio, usa:
std::vector<int>().swap(v);
C ++ 11
v.shrink_to_fit();
std::find usa el operator== para comparar elementos para la igualdad. Devuelve un iterador al
primer elemento en el rango que se compara igual al valor.
C ++ 11
C ++ 11
https://riptutorial.com/es/home 804
std::vector<int> v { 5, 4, 3, 2, 1 };
Si necesita realizar muchas búsquedas en un vector grande, puede considerar ordenar primero el
vector, antes de usar el algoritmo de binary_search .
Para encontrar el primer elemento en un vector que satisface una condición, se puede usar
std::find_if . Además de los dos parámetros dados a std::find , std::find_if acepta un tercer
argumento que es un objeto de función o puntero de función a una función de predicado. El
predicado debe aceptar un elemento del contenedor como argumento y devolver un valor
convertible a bool , sin modificar el contenedor:
C ++ 11
struct moreThan {
moreThan(int limit) : _limit(limit) {}
int _limit;
};
C ++ 11
https://riptutorial.com/es/home 805
Una matriz se puede convertir fácilmente en un std::vector usando std::begin y std::end :
C ++ 11
for(auto &x: v)
std::cout << x << " ";
std::cout << std::endl;
12345
El estándar (sección 23.3.7) especifica que se proporciona una especialización del vector<bool> ,
que optimiza el espacio al empaquetar los valores bool , de modo que cada uno ocupa solo un bit.
Dado que los bits no son direccionables en C ++, esto significa que varios requisitos en el vector
no se colocan en el vector<bool> :
• No se requiere que los datos almacenados sean contiguos, por lo que no se puede pasar un
vector<bool> a una API de C que espera una matriz bool .
• at() , operator [] y la anulación de referencias de los iteradores no devuelven una
referencia a bool . Más bien, devuelven un objeto proxy que (de manera imperfecta) simula
una referencia a un bool al sobrecargar sus operadores de asignación. Como ejemplo, es
posible que el siguiente código no sea válido para std::vector<bool> , porque al eliminar la
referencia a un iterador no se devuelve una referencia:
C ++ 11
De manera similar, las funciones que esperan un bool& argumento no se pueden usar con el
resultado del operator [] o at() aplicado al vector<bool> , o con el resultado de la
desreferenciación de su iterador:
https://riptutorial.com/es/home 806
void f(bool& b);
f(v[0]); // error
f(*v.begin()); // error
Nota: el siguiente ejemplo muestra los posibles valores bitwise de bytes individuales en un
vector<bool> tradicional vs. optimizado. Esto no siempre será cierto en todas las arquitecturas. Sin
embargo, es una buena manera de visualizar la optimización. En los ejemplos siguientes, un byte
se representa como [x, x, x, x, x, x, x, x].
C ++ 11
std::vector<char> trad_vect = {true, false, false, false, true, false, true, true};
Representación bitwise:
C ++ 11
std::vector<bool> optimized_vect = {true, false, false, false, true, false, true, true};
Representación bitwise:
[1,0,0,0,1,0,1,1]
1. El tamaño del vector actual es consultado por la función miembro size() . La función
https://riptutorial.com/es/home 807
empty() conveniencia devuelve true si el tamaño es 0:
vector<int> v = { 1, 2, 3 }; // size is 3
const vector<int>::size_type size = v.size();
cout << size << endl; // prints 3
cout << boolalpha << v.empty() << endl; // prints false
vector<int> v; // size is 0
cout << v.size() << endl; // prints 0
vector<int> v;
const vector<int>::size_type max_size = v.max_size();
cout << max_size << endl; // prints some large number
v.resize( max_size ); // probably won't work
v.push_back( 1 ); // definitely won't work
// !!!bad!!!evil!!!
vector<int> v_bad( N, 1 ); // constructs large N size vector
for( int i = 0; i < v_bad.size(); ++i ) { // size is not supposed to be int!
do_something( v_bad[i] );
}
La capacidad del vector difiere del tamaño . Mientras que el tamaño es simplemente cuántos
elementos tiene el vector actualmente, la capacidad es para cuántos elementos asignó / reservó
la memoria. Esto es útil porque las (re) asignaciones demasiado frecuentes de tamaños
demasiado grandes pueden ser costosas.
https://riptutorial.com/es/home 808
// !!!bad!!!evil!!!
vector<int> v_bad;
for( int i = 0; i < 10000; ++i ) {
v_bad.push_back( i ); // possibly lot of reallocations
}
// good
vector<int> v_good;
v_good.reserve( 10000 ); // good! only one allocation
for( int i = 0; i < 10000; ++i ) {
v_good.push_back( i ); // no allocations needed anymore
}
Vectores de concatenacion
Sin embargo, esta solución falla si intenta anexarse un vector a sí mismo, porque el estándar
especifica que los iteradores dados a insert() no deben ser del mismo rango que los elementos
del objeto receptor.
c ++ 11
En lugar de usar las funciones miembro del vector, las funciones std::begin() y std::end() se
https://riptutorial.com/es/home 809
pueden usar:
Esta es una solución más general, por ejemplo, porque b también puede ser una matriz. Sin
embargo, también esta solución no le permite agregar un vector a sí mismo.
std::vector<int>(v).swap(v);
C ++ 11
v.shrink_to_fit();
https://riptutorial.com/es/home 810
El encabezado <algorithm> proporciona una serie de funciones útiles para trabajar con vectores
ordenados.
Un requisito previo importante para trabajar con vectores ordenados es que los valores
almacenados sean comparables con < .
std::vector<int> v;
// add some code here to fill v with some elements
std::sort(v.begin(), v.end());
Los vectores std::lower_bound() permiten una búsqueda eficiente de elementos usando la función
std::lower_bound() . A diferencia de std::find() , esto realiza una búsqueda binaria eficiente en el
vector. El inconveniente es que solo proporciona resultados válidos para rangos de entrada
ordenados:
Si necesita insertar muchos elementos a la vez, podría ser más eficiente llamar a push_back() para
todos ellos primero y luego llamar a std::sort() una vez que se hayan insertado todos los
elementos. En este caso, el aumento del costo de la clasificación puede compensar el costo
reducido de insertar nuevos elementos al final del vector y no en el medio.
Si su vector contiene múltiples elementos del mismo valor, std::lower_bound() intentará devolver
un iterador al primer elemento del valor buscado. Sin embargo, si necesita insertar un nuevo
elemento después del último elemento del valor buscado, debe usar la función std::upper_bound()
ya que esto causará un menor desplazamiento de los elementos:
Si necesita los iteradores de límite superior e inferior, puede usar la función std::equal_range()
para recuperar ambos de manera eficiente con una llamada:
std::pair<std::vector<int>::iterator,
std::vector<int>::iterator> rg = std::equal_range(v.begin(), v.end(), 42);
std::vector<int>::iterator lower_bound = rg.first;
std::vector<int>::iterator upper_bound = rg.second;
https://riptutorial.com/es/home 811
Para probar si un elemento existe en un vector ordenado (aunque no es específico de los
vectores), puede usar la función std::binary_search() :
C ++ 11
En C ++ 11, los compiladores deben moverse implícitamente desde una variable local que se está
devolviendo. Además, la mayoría de los compiladores pueden realizar copias de la copia en
muchos casos y evitar el movimiento por completo. Como resultado de esto, devolver objetos
grandes que se pueden mover a bajo costo ya no requiere un manejo especial:
#include <vector>
#include <iostream>
// print vector
for (auto value : vec)
std::cout << value << " "; // this will print "1 2 3 4 5 6 7 8 9 10 "
return 0;
}
C ++ 11
#include <vector>
#include <iostream>
https://riptutorial.com/es/home 812
v.reserve(b-a+1);
for (int i = a; i <= b; i++) {
v.push_back(i);
}
}
// fill vector
fillVectorFrom_By_Ref(1, 10, vec);
// print vector
for (std::vector<int>::const_iterator it = vec.begin(); it != vec.end(); ++it)
std::cout << *it << " "; // this will print "1 2 3 4 5 6 7 8 9 10 "
std::cout << std::endl;
return 0;
}
Para encontrar el elemento más grande o más pequeño almacenado en un vector, puede usar los
métodos std::max_element y std::min_element , respectivamente. Estos métodos se definen en el
encabezado <algorithm> . Si varios elementos son equivalentes al elemento más grande (el más
pequeño), los métodos devuelven el iterador al primer elemento de este tipo. Devuelve v.end()
para vectores vacíos.
std::cout << "maxElementIndex:" << maxElementIndex << ", maxElement:" << maxElement << '\n';
std::cout << "minElementIndex:" << minElementIndex << ", minElement:" << minElement << '\n';
Salida:
maxElementIndex: 3, maxElement: 10
minElementIndex: 1, minElement: 2
C ++ 11
Salida:
https://riptutorial.com/es/home 813
elemento minimo: 2
elemento máximo: 10
Los vectores se pueden utilizar como una matriz 2D definiéndolos como un vector de vectores.
Una matriz con 3 filas y 4 columnas con cada celda inicializada como 0 puede definirse como:
C ++ 11
La sintaxis para inicializarlos utilizando listas de inicializadores o de otro modo son similares a las
de un vector normal.
La iteración en toda la matriz es similar a la de un vector normal pero con una dimensión
adicional.
C ++ 11
Un vector de vectores es una forma conveniente de representar una matriz, pero no es el más
eficiente: los vectores individuales están dispersos alrededor de la memoria y la estructura de
datos no es compatible con el caché.
Además, en una matriz adecuada, la longitud de cada fila debe ser la misma (este no es el caso
https://riptutorial.com/es/home 814
de un vector de vectores). La flexibilidad adicional puede ser una fuente de errores.
https://riptutorial.com/es/home 815
Capítulo 135: Técnicas de refactorización
Introducción
Refactorización se refiere a la modificación del código existente en una versión mejorada. Aunque
la refactorización a menudo se realiza mientras se cambia el código para agregar características
o corregir errores, el término en particular se refiere a mejorar el código sin necesariamente
agregar características o corregir errores.
Examples
Recorrer la refactorización
Aquí hay un programa que podría beneficiarse de la refactorización. Es un programa simple que
utiliza C ++ 11, cuyo objetivo es calcular e imprimir todos los números primos del 1 al 100 y se
basa en un programa que se publicó en CodeReview para su revisión.
#include <iostream>
#include <vector>
#include <cmath>
int main()
{
int l = 100;
bool isprime;
std::vector<int> primes;
primes.push_back(2);
for (int no = 3; no < l; no += 2) {
isprime = true;
for (int primecount=0; primes[primecount] <= std::sqrt(no); ++primecount) {
if (no % primes[primecount] == 0) {
isprime = false;
break;
} else if (primes[primecount] * primes[primecount] > no) {
std::cout << no << "\n";
break;
}
}
if (isprime) {
std::cout << no << " ";
primes.push_back(no);
}
}
std::cout << "\n";
}
3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
Lo primero que notamos es que el programa no imprime 2 que es un número primo. Podríamos
https://riptutorial.com/es/home 816
simplemente agregar una línea de código para simplemente imprimir esa constante sin modificar
el resto del programa, pero sería mejor refactorizar el programa para dividirlo en dos partes: una
que crea la lista de números primos y otra que las imprime. . Así es como podría verse:
#include <iostream>
#include <vector>
#include <cmath>
int main()
{
std::vector<int> primes = prime_list(100);
for (std::size_t i = 0; i < primes.size(); ++i) {
std::cout << primes[i] << ' ';
}
std::cout << '\n';
}
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
https://riptutorial.com/es/home 817
if (no % primes[primecount] == 0) {
isprime = false;
break;
}
}
if (isprime) {
primes.push_back(no);
}
}
return primes;
}
Podemos ir más lejos, cambiando los nombres de las variables para que sean un poco más
descriptivos. Por ejemplo, primecount no es realmente un conteo de primos. En su lugar, es una
variable de índice en el vector de números primos conocidos. Asimismo, si bien no se utiliza a
veces como una abreviatura de "Número", en la escritura matemática, es más común el uso de n .
También podemos hacer algunas modificaciones eliminando la break y declarando las variables
más cerca de donde se usan.
También podemos refactorizar main para usar un "rango para" para hacerlo un poco más limpio:
int main()
{
std::vector<int> primes = prime_list(100);
for (auto p : primes) {
std::cout << p << ' ';
}
std::cout << '\n';
}
Esta es solo una de las formas en que se puede realizar la refactorización. Otros pueden hacer
elecciones diferentes. Sin embargo, el propósito de la refactorización sigue siendo el mismo, que
es mejorar la legibilidad y posiblemente el rendimiento del código sin necesariamente agregar
características.
Ir a la limpieza
En las bases de código C ++ que solían ser C, uno puede encontrar el patrón que se va a goto
cleanup . Como el comando goto hace que el flujo de trabajo de una función sea más difícil de
https://riptutorial.com/es/home 818
entender, a menudo esto se evita. A menudo, puede ser reemplazado por declaraciones de
devolución, bucles, funciones. Sin embargo, con la goto cleanup uno necesita deshacerse de la
lógica de limpieza.
result = TRUE;
cleanup:
delete [] vec;
return result;
}
return TRUE;
}
A partir de este punto, uno podría continuar refactorizando el código real. Por ejemplo,
reemplazando el VectorRAII por std::unique_ptr o std::vector .
https://riptutorial.com/es/home 819
Capítulo 136: Tipo de borrado
Introducción
El borrado de tipo es un conjunto de técnicas para crear un tipo que puede proporcionar una
interfaz uniforme a varios tipos subyacentes, mientras oculta la información de tipo subyacente
del cliente. std::function<R(A...)> , que tiene la capacidad de contener objetos de diferentes
tipos, es quizás el mejor ejemplo conocido de borrado de tipo en C ++.
Examples
Mecanismo basico
El borrado de tipo es una forma de ocultar el tipo de un objeto del código que lo usa, aunque no
se derive de una clase base común. Al hacerlo, proporciona un puente entre los mundos del
polimorfismo estático (plantillas; en el lugar de uso, el tipo exacto se debe conocer en el momento
de la compilación, pero no es necesario declararlo para que se ajuste a una interfaz en la
definición) y el polimorfismo dinámico. (herencia y funciones virtuales; en el lugar de uso, no es
necesario conocer el tipo exacto en el momento de la compilación, pero debe declararse que se
ajusta a una interfaz en la definición).
#include <ostream>
class Printable
{
public:
template <typename T>
Printable(T value) : pValue(new Value<T>(value)) {}
~Printable() { delete pValue; }
void print(std::ostream &os) const { pValue->print(os); }
private:
Printable(Printable const &) /* in C++1x: =delete */; // not implemented
void operator = (Printable const &) /* in C++1x: =delete */; // not implemented
struct ValueBase
{
virtual ~ValueBase() = default;
virtual void print(std::ostream &) const = 0;
};
template <typename T>
struct Value : ValueBase
{
Value(T const &t) : v(t) {}
virtual void print(std::ostream &os) const { os << v; }
T v;
};
ValueBase *pValue;
};
https://riptutorial.com/es/home 820
En el sitio de uso, solo la definición anterior debe estar visible, al igual que con las clases base
con funciones virtuales. Por ejemplo:
#include <iostream>
Tenga en cuenta que esto no es una plantilla, sino una función normal que solo se debe declarar
en un archivo de encabezado y se puede definir en un archivo de implementación (a diferencia de
las plantillas, cuya definición debe ser visible en el lugar de uso).
En la definición del tipo concreto, no hay que saber nada acerca de Printable , solo debe
ajustarse a una interfaz, al igual que con las plantillas:
MyType foo = { 42 };
print_value(foo);
Un tipo Regular es un tipo que se puede construir y asignar y asignar a través de copiar o mover,
se puede destruir y se puede comparar igual a. También se puede construir a partir de ningún
argumento. Finalmente, también tiene soporte para algunas otras operaciones que son muy útiles
en varios algoritmos y contenedores std .
https://riptutorial.com/es/home 821
std::size_t(*hash)(void const* self); // std::hash<T>{}(T const&)
std::type_info const&(*type)(); // typeid(T)
dtor_unique_ptr(*clone)(void const* self); // T(T const&)
};
template<class T>
regular_vtable make_regular_vtable() noexcept {
return {
[](void* dest, void const* src){ *static_cast<T*>(dest) = *static_cast<T const*>(src); },
[](void* dest, void* src){ *static_cast<T*>(dest) = std::move(*static_cast<T*>(src)); },
[](void const* lhs, void const* rhs){ return *static_cast<T const*>(lhs) == *static_cast<T
const*>(rhs); },
[](void const* lhs, void const* rhs) { return std::less<T>{}(*static_cast<T
const*>(lhs),*static_cast<T const*>(rhs)); },
[](void const* self){ return std::hash<T>{}(*static_cast<T const*>(self)); },
[]()->decltype(auto){ return typeid(T); },
[](void const* self){ return make_dtor_unique_ptr<T>(*static_cast<T const*>(self)); }
};
}
template<class T>
regular_vtable const* get_regular_vtable() noexcept {
static const regular_vtable vtable=make_regular_vtable<T>();
return &vtable;
}
struct regular_type {
using self=regular_type;
regular_vtable const* vtable = 0;
dtor_unique_ptr ptr{nullptr, [](void*){}};
template<class T, class...Args>
void emplace( Args&&... args ) {
ptr = make_dtor_unique_ptr<T>(std::forward<Args>(args)...);
if (ptr)
vtable = get_regular_vtable<T>();
else
vtable = nullptr;
}
friend bool operator==(regular_type const& lhs, regular_type const& rhs) {
if (lhs.vtable != rhs.vtable) return false;
return lhs.vtable->equals( lhs.ptr.get(), rhs.ptr.get() );
}
bool before(regular_type const& rhs) const {
auto const& lhs = *this;
if (!lhs.vtable || !rhs.vtable)
return std::less<regular_vtable const*>{}(lhs.vtable,rhs.vtable);
if (lhs.vtable != rhs.vtable)
return lhs.vtable->type().before(rhs.vtable->type());
return lhs.vtable->order( lhs.ptr.get(), rhs.ptr.get() );
}
// technically friend bool operator< that calls before is also required
https://riptutorial.com/es/home 822
{
o.vtable = nullptr;
}
friend void swap(regular_type& lhs, regular_type& rhs){
std::swap(lhs.ptr, rhs.ptr);
std::swap(lhs.vtable, rhs.vtable);
}
regular_type& operator=(regular_type&& o) {
if (o.vtable == vtable) {
vtable->move_assign(ptr.get(), o.ptr.get());
return *this;
}
auto tmp = std::move(o);
swap(*this, tmp);
return *this;
}
regular_type(regular_type const& o):
vtable(o.vtable),
ptr(o.vtable?o.vtable->clone(o.ptr.get()):dtor_unique_ptr{nullptr, [](void*){}})
{
if (!ptr && vtable) vtable = nullptr;
}
regular_type& operator=(regular_type const& o) {
if (o.vtable == vtable) {
vtable->copy_assign(ptr.get(), o.ptr.get());
return *this;
}
auto tmp = o;
swap(*this, tmp);
return *this;
}
std::size_t hash() const {
if (!vtable) return 0;
return vtable->hash(ptr.get());
}
template<class T,
std::enable_if_t< !std::is_same<std::decay_t<T>, regular_type>{}, int>* =nullptr
>
regular_type(T&& t) {
emplace<std::decay_t<T>>(std::forward<T>(t));
}
};
namespace std {
template<>
struct hash<regular_type> {
std::size_t operator()( regular_type const& r )const {
return r.hash();
}
};
template<>
struct less<regular_type> {
bool operator()( regular_type const& lhs, regular_type const& rhs ) const {
return lhs.before(rhs);
}
};
}
ejemplo vivo .
Dicho tipo regular se puede usar como una clave para un std::map o un std::unordered_map que
https://riptutorial.com/es/home 823
acepta cualquier regular para una clave, como:
std::map<regular_type, std::any>
std::function tipo de std::function borra hasta unas pocas operaciones. Una de las cosas que
requiere es que el valor almacenado sea copiable.
Esto causa problemas en algunos contextos, como lambdas que almacenan ptrs únicos. Si está
utilizando la std::function en un contexto en el que no importa la copia, como un grupo de
subprocesos donde se envían tareas a subprocesos, este requisito puede agregar una
sobrecarga.
De task la task . Esto demuestra cómo se puede escribir un tipo de std::function simple. Omití el
constructor de copia (lo que implicaría agregar un método de clone a los details::task_pimpl<...>
también).
template<class Sig>
struct task;
https://riptutorial.com/es/home 824
task_pimpl_impl( Fin&& fin ):f(std::forward<Fin>(fin)) {}
virtual R invoke(Args&&...args) const final override {
return f(std::forward<Args>(args)...);
}
virtual const std::type_info& target_type() const final override {
return typeid(F);
}
};
template<class R, class...Args>
struct task<R(Args...)> {
// semi-regular:
task()=default;
task(task&&)=default;
// no copy
private:
// aliases to make some SFINAE code below less ugly:
template<class F>
using call_r = std::result_of_t<F const&(Args...)>;
template<class F>
using is_task = std::is_same<std::decay_t<F>, task>;
public:
// can be constructed from a callable F
template<class F,
// that can be invoked with Args... and converted-to-R:
class= decltype( (R)(std::declval<call_r<F>>()) ),
// and is not this same type:
std::enable_if_t<!is_task<F>{}, int>* = nullptr
>
task(F&& f):
m_pImpl( make_pimpl(std::forward<F>(f)) )
{}
https://riptutorial.com/es/home 825
m_pImpl = make_pimpl(std::forward<F>(f));
}
// Part of the std::function interface:
const std::type_info& target_type() const {
if (!*this) return typeid(void);
return m_pImpl->target_type();
}
template< class T >
T* target() {
return target_impl<T>();
}
template< class T >
const T* target() const {
return target_impl<T>();
}
// compare with nullptr :
friend bool operator==( std::nullptr_t, task const& self ) { return !self; }
friend bool operator==( task const& self, std::nullptr_t ) { return !self; }
friend bool operator!=( std::nullptr_t, task const& self ) { return !!self; }
friend bool operator!=( task const& self, std::nullptr_t ) { return !!self; }
private:
template<class T>
using pimpl_t = details::task_pimpl_impl<T, R, Args...>;
template<class F>
static auto make_pimpl( F&& f ) {
using dF=std::decay_t<F>;
using pImpl_t = pimpl_t<dF>;
return std::make_unique<pImpl_t>(std::forward<F>(f));
}
std::unique_ptr<details::task_pimpl<R,Args...>> m_pImpl;
Para hacer que esta biblioteca valga la pena, querría agregar una pequeña optimización de búfer,
para que no almacene todos los invocables en el montón.
No todo el borrado de tipos implica herencia virtual, asignaciones, ubicación nueva o incluso
punteros a funciones.
Lo que hace que se borre el tipo de borrado de tipo es que describe un (conjunto de)
comportamiento (s), y toma cualquier tipo que admita ese comportamiento y lo envuelve. Toda la
información que no está en ese conjunto de comportamientos es "olvidada" o "borrada".
https://riptutorial.com/es/home 826
Una array_view toma su rango entrante o tipo de contenedor y borra todo excepto el hecho de que
es un búfer contiguo de T
template<class T>
struct array_view {
// the core of the class:
T* b=nullptr;
T* e=nullptr;
T* begin() const { return b; }
T* end() const { return e; }
// useful helpers that let you generate other ranges from this one
// quickly and safely:
array_view without_front( std::size_t i=1 ) const {
i = (std::min)(i, size());
return {begin()+i, end()};
}
array_view without_back( std::size_t i=1 ) const {
i = (std::min)(i, size());
return {begin(), end()-i};
}
// final constructor:
array_view(T* s, T* f):b(s),e(f) {}
// start and length is useful in my experience:
array_view(T* s, std::size_t length):array_view(s, s+length) {}
// array constructor:
https://riptutorial.com/es/home 827
template<std::size_t N>
array_view( T(&arr)[N] ):array_view(arr, N) {}
una array_view toma cualquier contenedor que admita .data() devolviendo un puntero a T y un
método .size() , o una matriz, y lo borra a un rango de acceso aleatorio sobre T s contiguos.
En este caso, los datos que podemos extraer de la cosa que estamos borrando, junto con nuestro
estado no propietario "vista", significa que no tenemos que asignar memoria o escribir funciones
personalizadas dependientes de los tipos.
Ejemplo vivo .
Una mejora sería utilizar data no miembros y un size no miembros en un contexto habilitado para
ADL.
super_any<decltype(print)> a = 7;
(a->*print)(std::cout);
Esto crea un tipo de puntero de función y una fábrica para dichos punteros de función, dado un
https://riptutorial.com/es/home 828
any_method :
template<class any_method>
using any_sig_from_method = typename any_method::signature;
Queremos poder escribir más de un método a la vez. Así que los agrupamos en una tupla, y
escribimos un envoltorio de ayuda para pegar la tupla en el almacenamiento estático por tipo y
mantener un puntero a ellos.
template<class...any_methods>
using any_method_tuple = std::tuple< typename any_method_function<any_methods>::type... >;
template<class...methods>
struct any_methods {
private:
any_method_tuple<methods...> const* vtable = 0;
template<class T>
static any_method_tuple<methods...> const* get_vtable( tag_t<T> ) {
static const auto table = make_vtable<methods...>(tag<T>);
return &table;
}
public:
any_methods() = default;
template<class T>
any_methods( tag_t<T> ): vtable(get_vtable(tag<T>)) {}
any_methods& operator=(any_methods const&)=default;
template<class T>
https://riptutorial.com/es/home 829
void change_type( tag_t<T> ={} ) { vtable = get_vtable(tag<T>); }
template<class any_method>
auto get_invoker( tag_t<any_method> ={} ) const {
return std::get<typename any_method_function<any_method>::type>( *vtable );
}
};
Podríamos especializarlo en casos en los que el vtable es pequeño (por ejemplo, 1 elemento), y
usar punteros directos almacenados en clase en esos casos por eficiencia.
template<class...methods>
struct super_any_t;
Esto busca los métodos que Super Super admite para SFINAE y mejores mensajes de error:
o en C ++ 17:
https://riptutorial.com/es/home 830
Tenga en cuenta que usar un dispositivo no lambda puede hacer que las cosas se pongan
peludas, ya que usamos el tipo para un paso de búsqueda. Esto se puede arreglar, pero haría
este ejemplo más largo de lo que ya es. Por lo tanto, siempre inicialice cualquier método desde un
lambda, o desde un tipo parametarizado en un lambda.
template<class Any,
// SFINAE testing that one of the Anys's matches this type:
std::enable_if_t< super_method_applies< Any&&, any_method >{}, int>* =nullptr
>
friend auto operator->*( Any&& self, any_method const& m ) {
// we don't use the value of the any_method, because each any_method has
// a unique type (!) and we check that one of the auto*'s in the super_any
// already has a pointer to us. We then dispatch to the corresponding
// any_method_data...
template<class...Args>
decltype(auto) operator()(Args&&...args)const {
return f(std::forward<Args>(args)...);
}
};
Este es el aumento de any . Es a la vez una any , y lleva en torno a un conjunto de punteros a
funciones de tipo de borrado que cambian cada vez que el contenido any lo hace:
template<class... methods>
struct super_any_t:boost::any, any_methods<methods...> {
using vtable=any_methods<methods...>;
public:
template<class T,
std::enable_if_t< !std::is_base_of<super_any_t, std::decay_t<T>>{}, int> =0
>
super_any_t( T&& t ):
boost::any( std::forward<T>(t) )
https://riptutorial.com/es/home 831
{
using dT=std::decay_t<T>;
this->change_type( tag<dT> );
}
template<class T,
std::enable_if_t< !std::is_same<std::decay_t<T>, super_any_t>{}, int>* =nullptr
>
super_any_t& operator=( T&& t ) {
((boost::any&)*this) = std::forward<T>(t);
using dT=std::decay_t<T>;
this->change_type( tag<dT> );
return *this;
}
};
Debido a que almacenamos los any_method s como objetos const , esto hace que hacer un
super_any un poco más fácil:
template<class...Ts>
using super_any = super_any_t< std::remove_cv_t<Ts>... >;
Código de prueba:
int main()
{
super_any<decltype(print), decltype(wprint)> a = 7;
super_any<decltype(print), decltype(wprint)> a2 = 7;
(a->*print)(std::cout);
(a->*wprint)(std::wcout);
https://riptutorial.com/es/home 832
}
ejemplo vivo .
https://riptutorial.com/es/home 833
Capítulo 137: Tipo de Devolución Covarianza
Observaciones
La covarianza de un parámetro o un valor de retorno para una función miembro virtual m es
donde su tipo T vuelve más específico en el reemplazo de m de una clase derivada. El tipo T luego
varía ( varianza ) en especificidad de la misma manera ( co ) que las clases que proporcionan m .
C ++ proporciona soporte de lenguaje para los tipos de retorno covariantes que son punteros en
bruto o referencias en bruto, la covarianza es para el tipo de pointee o referente.
El soporte de C ++ está limitado a los tipos devueltos porque los valores de retorno de la función
son los únicos argumentos de salida puros en C ++, y la covarianza solo es segura para un
argumento de salida puro. De lo contrario, el código de llamada podría proporcionar un objeto de
un tipo menos específico del que espera el código de recepción. La profesora del MIT Barbara
Liskov investigó esto y relacionó los problemas de seguridad de tipo de varianza, y ahora se
conoce como el Principio de Sustitución de Liskov, o LSP .
Dado que los punteros inteligentes son del tipo de clase, no se puede usar el soporte integrado
para la covarianza directamente para los resultados del puntero inteligente, pero se pueden definir
funciones de envoltura de resultados del puntero inteligente no virtual aparentemente
covariantes para una función virtual covariante que produce punteros en bruto.
Examples
1. Ejemplo de base sin devoluciones covariantes, muestra por qué son
deseables
// 1. Base example not using language support for covariance, dynamic type checking.
class Top
{
public:
virtual Top* clone() const = 0;
virtual ~Top() = default; // Necessary for `delete` via Top*.
};
public:
Top* clone() const override
{ return new D( *this ); }
};
class DD : public D
{
https://riptutorial.com/es/home 834
private:
int answer_ = 42;
public:
int answer() const
{ return answer_;}
#include <assert.h>
#include <iostream>
#include <typeinfo>
using namespace std;
int main()
{
cout << boolalpha;
class Top
{
public:
virtual Top* clone() const = 0;
virtual ~Top() = default; // Necessary for `delete` via Top*.
};
class DD : public D
{
private:
int answer_ = 42;
public:
int answer() const
{ return answer_;}
https://riptutorial.com/es/home 835
DD* /* ← Covariant return */ clone() const override
{ return new DD( *this ); }
};
#include <iostream>
using namespace std;
int main()
{
DD* p1 = new DD();
DD* p2 = p1->clone();
// Correct dynamic type DD for *p2 is guaranteed by the static type checking.
#include <memory>
using std::unique_ptr;
class Top
{
private:
virtual Top* virtual_clone() const = 0;
public:
unique_ptr<Top> clone() const
{ return up( virtual_clone() ); }
public:
unique_ptr<D> /* ← Apparent covariant return */ clone() const
{ return up( virtual_clone() ); }
};
class DD : public D
{
private:
int answer_ = 42;
https://riptutorial.com/es/home 836
public:
int answer() const
{ return answer_;}
#include <iostream>
using namespace std;
int main()
{
auto p1 = unique_ptr<DD>(new DD());
auto p2 = p1->clone();
// Correct dynamic type DD for *p2 is guaranteed by the static type checking.
https://riptutorial.com/es/home 837
Capítulo 138: Tipo de rasgos
Observaciones
Los rasgos de tipo son construcciones con plantillas que se utilizan para comparar y probar las
propiedades de diferentes tipos en el momento de la compilación. Se pueden usar para
proporcionar una lógica condicional en el momento de la compilación que puede limitar o ampliar
la funcionalidad de su código de una manera específica. La biblioteca de rasgos de tipo se
incorporó con el estándar c++11 , que proporciona una serie de funcionalidades diferentes.
También es posible crear sus propias plantillas de comparación de rasgos de tipo.
Examples
Rasgos de tipo estándar
C ++ 11
Estos rasgos se usan normalmente en las plantillas para verificar errores de los usuarios, admitir
la programación genérica y permitir optimizaciones.
La mayoría de los rasgos de tipo se utilizan para verificar si un tipo cumple con algunos criterios.
Estos tienen la siguiente forma:
Si la clase de plantilla está instanciada con un tipo que cumple con algunos criterios foo ,
is_foo<T> hereda de std::integral_constant<bool,true> (también conocido como std::true_type ),
de lo contrario, hereda de std::integral_constant<bool,false> (también conocido como
std::false_type ). Esto le da al rasgo los siguientes miembros:
Constantes
static constexpr bool value
Funciones
operator bool
https://riptutorial.com/es/home 838
Devuelve value
C ++ 14
bool operator()
Devuelve value
Los tipos
Nombre Definición
value_type bool
type std::integral_constant<bool,value>
También hay varios rasgos que transforman los tipos, como std::add_pointer y
std::underlying_type . Estos rasgos generalmente exponen un type miembro de tipo único que
contiene el tipo transformado. Por ejemplo, std::add_pointer<int>::type is int* .
C ++ 11
La relación de tipo std::is_same<T, T> se utiliza para comparar dos tipos. Se evaluará como
booleano, verdadero si los tipos son los mismos y falso en caso contrario.
p.ej
https://riptutorial.com/es/home 839
// Prints true on most x86 and x86_64 compilers.
std::cout << std::is_same<int, int32_t>::value << "\n";
// Prints false on all compilers.
std::cout << std::is_same<float, int>::value << "\n";
// Prints false on all compilers.
std::cout << std::is_same<unsigned int, int>::value << "\n";
p.ej
Usar std::is_same para advertir cuando se usa incorrectamente una clase o función con
plantilla.
Cuando se combina con una std::is_same estática, la plantilla std::is_same puede ser una
herramienta valiosa para imponer el uso adecuado de las clases y funciones de plantilla.
por ejemplo, una función que solo permite la entrada desde un int y una opción de dos
estructuras.
#include <type_traits>
struct foo {
int member;
// Other variables
};
struct bar {
char member;
};
template<typename T>
int AddStructMember(T var1, int var2) {
// If type T != foo || T != bar then show error message.
static_assert(std::is_same<T, foo>::value ||
std::is_same<T, bar>::value,
"This function does not support the specified type.");
return var1.member + var2;
}
C ++ 11
Hay una serie de rasgos de tipos diferentes que comparan tipos más generales.
Es integral:
https://riptutorial.com/es/home 840
Evalúa como verdadero para todos los tipos de enteros int , char , long , unsigned int etc.
Es punto flotante:
Evalúa como verdadero para todos los tipos de punto flotante. float , double , long double etc.
Es enum:
Evalúa como verdadero para todos los tipos enumerados, incluida la enum class .
Es puntero:
Es la clase:
Se evalúa como verdadero para todas las clases y estructuras, con la excepción de la enum class .
Tipo de propiedades
C ++ 11
https://riptutorial.com/es/home 841
Las propiedades de tipo comparan los modificadores que se pueden colocar sobre diferentes
variables. La utilidad de estos rasgos de tipo no siempre es obvia.
Nota: El ejemplo a continuación solo ofrecería una mejora en un compilador que no optimiza. Es
una simple prueba de concepto, en lugar de un ejemplo complejo.
template<typename T>
inline T FastDivideByFour(cont T &var) {
// Will give an error if the inputted type is not an unsigned integral type.
static_assert(std::is_unsigned<T>::value && std::is_integral<T>::value,
"This function is only designed for unsigned integral types.");
return (var >> 2);
}
Es constante:
Es volátil:
Está firmado:
https://riptutorial.com/es/home 842
Capítulo 139: Tipo de retorno final
Sintaxis
• function_name ([ function_args ]) [ function_attributes ] [ function_qualifiers ] -> trailing-
return-type [ require_clause ]
Observaciones
La sintaxis anterior muestra una declaración de función completa que utiliza un tipo final, donde
los corchetes indican una parte opcional de la declaración de función (como la lista de
argumentos si es una función sin argumento).
Además, la sintaxis del tipo de retorno final prohíbe la definición de una clase, unión o tipo de
enumeración dentro de un tipo de retorno final (tenga en cuenta que esto tampoco está permitido
en un tipo de retorno inicial). Aparte de eso, los tipos pueden escribirse de la misma manera
después de -> como sería en cualquier otro lugar.
Examples
Evite calificar un nombre de tipo anidado
class ClassWithAReallyLongName {
public:
class Iterator { /* ... */ };
Iterator end();
};
El tipo de retorno final se busca en el alcance de la clase, mientras que el tipo de retorno inicial se
busca en el ámbito de espacio de nombres adjunto y, por lo tanto, puede requerir una calificación
"redundante".
Expresiones lambda
Un lambda solo puede tener un tipo de retorno final; la sintaxis de tipo de retorno inicial no es
aplicable a las lambdas. Tenga en cuenta que en muchos casos no es necesario especificar un
tipo de retorno para un lambda en absoluto.
https://riptutorial.com/es/home 843
struct Base {};
struct Derived1 : Base {};
struct Derived2 : Base {};
auto lambda = [](bool b) -> Base* { if (b) return new Derived1; else return new Derived2; };
// ill-formed: auto lambda = Base* [](bool b) { ... };
https://riptutorial.com/es/home 844
Capítulo 140: Tipos atómicos
Sintaxis
• std :: atómico <T>
• std :: atomic_flag
Observaciones
std::atomic permite el acceso atómico a un tipo TriviallyCopyable, es dependiente de la
implementación si esto se realiza mediante operaciones atómicas o mediante el uso de bloqueos.
El único tipo atómico sin bloqueo garantizado es std::atomic_flag .
Examples
Acceso multihilo
Se puede usar un tipo atómico para leer y escribir de forma segura en una ubicación de memoria
compartida entre dos subprocesos.
#include <thread>
#include <iostream>
//function will add all values including and between 'a' and 'b' to 'result'
void add(int a, int b, int * result) {
for (int i = a; i <= b; i++) {
*result += i;
}
}
int main() {
//a primitive data type has no thread safety
int shared = 0;
https://riptutorial.com/es/home 845
//rejoin the thread at the end of execution for cleaning purposes
addingThread.join();
return 0;
}
El ejemplo anterior puede causar una lectura dañada y puede llevar a un comportamiento
indefinido.
#include <atomic>
#include <thread>
#include <iostream>
//function will add all values including and between 'a' and 'b' to 'result'
void add(int a, int b, std::atomic<int> * result) {
for (int i = a; i <= b; i++) {
//atomically add 'i' to result
result->fetch_add(i);
}
}
int main() {
//atomic template used to store non-atomic objects
std::atomic<int> shared = 0;
return 0;
}
El ejemplo anterior es seguro porque todas las operaciones de store() y load() del tipo de datos
atomic protegen el int encapsulado del acceso simultáneo.
https://riptutorial.com/es/home 846
Capítulo 141: Tipos sin nombre
Examples
Clases sin nombre
A diferencia de una clase o estructura con nombre, las clases y estructuras sin nombre deben
crearse instancias donde están definidas, y no pueden tener constructores o destructores.
struct {
int foo;
double bar;
} foobar;
foobar.foo = 5;
foobar.bar = 4.0;
class {
int baz;
public:
int buzz;
void setBaz(int v) {
baz = v;
}
} barbar;
barbar.setBaz(15);
barbar.buzz = 2;
Miembros anónimos
Como una extensión no estándar de C ++, los compiladores comunes permiten el uso de clases
como miembros anónimos.
struct Example {
struct {
int inner_b;
};
int outer_b;
//The anonymous struct's members are accessed as if members of the parent struct
Example() : inner_b(2), outer_b(4) {
inner_b = outer_b + 2;
}
};
Example ex;
//The same holds true for external code referencing the struct
ex.inner_b -= ex.outer_b;
https://riptutorial.com/es/home 847
Como un alias de tipo
Los tipos de clase sin nombre también se pueden usar al crear alias de tipo, es decir, a través de
typedef y using :
C ++ 11
typedef struct {
float x;
float y;
} vec2d;
vec2d pt;
pt.x = 4.f;
pt.y = 3.f;
Anonima union
Los nombres de los miembros de una unión anónima pertenecen al alcance de la declaración de
la unión y deben ser distintos a todos los demás nombres de este alcance. El ejemplo aquí tiene
la misma construcción que los miembros anónimos de ejemplo que usan "struct" pero es de
conformidad estándar.
struct Sample {
union {
int a;
int b;
};
int c;
};
int main()
{
Sample sa;
sa.a =3;
sa.b =4;
sa.c =5;
}
https://riptutorial.com/es/home 848
Capítulo 142: Typedef y alias de tipo
Introducción
El typedef y (desde C ++ 11) using palabras clave se pueden usar para dar un nuevo nombre a un
tipo existente.
Sintaxis
• typedef type-specifier-seq init-declarator-list ;
• atributo-especificador-seq typedef decl-especificador-seq init-declarator-list ; // desde C ++
11
• utilizando el identificador de atributo identificador -seq ( opt ) = id-tipo ; // desde C ++ 11
Examples
Sintaxis básica de typedef
Una declaración typedef tiene la misma sintaxis que una declaración de variable o función, pero
contiene la palabra typedef . La presencia de typedef hace que la declaración declare un tipo en
lugar de una variable o función.
Una vez que se ha definido un alias de tipo, se puede usar indistintamente con el nombre original
del tipo.
typedef nunca crea un tipo distinto. Solo da otra forma de referirse a un tipo existente.
struct S {
int f(int);
};
typedef int I;
// ok: defines int S::f(int)
I S::f(I x) { return x; }
https://riptutorial.com/es/home 849
La regla de que las declaraciones typedef tienen la misma sintaxis que las declaraciones de
variables y funciones ordinarias se pueden usar para leer y escribir declaraciones más complejas.
Esto es especialmente útil para construcciones con sintaxis confusa, como punteros a miembros
no estáticos.
void (Foo::*pmf)(int); // pmf has type "pointer to member function of Foo taking int
// and returning void"
typedef void (Foo::*pmf)(int); // pmf is an alias for "pointer to member function of Foo
// taking int and returning void"
La palabra clave typedef es un especificador, por lo que se aplica por separado a cada
declarador. Por lo tanto, cada nombre declarado se refiere al tipo que ese nombre tendría en
ausencia de typedef .
int *x, (*p)(); // x has type int*, and p has type int(*)()
typedef int *x, (*p)(); // x is an alias for int*, while p is an alias for int(*)()
C ++ 11
using I = int;
using A = int[100]; // array of 100 ints
using FP = void(*)(int); // pointer to function of int returning void
using MP = void (Foo::*)(int); // pointer to member function of Foo of int returning void
https://riptutorial.com/es/home 850
Crear un alias de tipo con el using tiene exactamente el mismo efecto que crear un alias de tipo
con typedef . Es simplemente una sintaxis alternativa para lograr lo mismo.
A diferencia de typedef , el using puede ser templado. Una "plantilla typedef" creada con el using
se llama una plantilla de alias .
https://riptutorial.com/es/home 851
Capítulo 143: Uniones
Observaciones
Los sindicatos son herramientas muy útiles, pero vienen con algunas advertencias importantes:
Una std::variant (desde C ++ 17) es como una unión, solo que le dice lo que contiene
actualmente (parte de su estado visible es el tipo de valor que tiene en un momento dado:
impone que el acceso al valor ocurra solo a ese tipo).
Examples
Características básicas de la unión
Los sindicatos son una estructura especializada dentro de la cual todos los miembros ocupan una
memoria superpuesta.
union U {
int a;
short b;
float c;
};
U u;
//Assigning to any union member changes the shared memory of all members
u.c = 4.f;
u.a = 5;
u.c != 4.f;
Uso tipico
Los sindicatos son útiles para minimizar el uso de la memoria para datos exclusivos, como
cuando se implementan tipos de datos mixtos.
struct AnyType {
enum {
IS_INT,
https://riptutorial.com/es/home 852
IS_FLOAT
} type;
union Data {
int as_int;
float as_float;
} value;
Comportamiento indefinido
union U {
int a;
short b;
float c;
};
U u;
u.a = 10;
if (u.b == 10) {
// this is undefined behavior since 'a' was the last member to be
// written to. A lot of compilers will allow this and might issue a
// warning, but the result will be "as expected"; this is a compiler
// extension and cannot be guaranteed across compilers (i.e. this is
// not compliant/portable code).
}
https://riptutorial.com/es/home 853
Capítulo 144: Usando std :: unordered_map
Introducción
std :: unordered_map es solo un contenedor asociativo. Funciona sobre las teclas y sus mapas.
Clave como van los nombres, ayuda a tener singularidad en el mapa. Mientras que el valor
asignado es solo un contenido que está asociado con la clave. Los tipos de datos de esta clave y
mapa pueden ser cualquiera de los tipos de datos predefinidos o definidos por el usuario.
Observaciones
Como su nombre indica, los elementos del mapa no ordenado no se almacenan en una secuencia
ordenada. Se almacenan de acuerdo con sus valores hash y, por lo tanto, el uso de un mapa
desordenado tiene muchos beneficios, ya que solo se necesita O (1) para buscar cualquier
elemento en él. También es más rápido que otros contenedores de mapas. También se puede ver
en el ejemplo que es muy fácil de implementar ya que el operador ([]) nos ayuda a acceder
directamente al valor asignado.
Examples
Declaración y uso
https://riptutorial.com/es/home 854
Lea Usando std :: unordered_map en línea:
https://riptutorial.com/es/cplusplus/topic/10540/usando-std----unordered-map
https://riptutorial.com/es/home 855
Capítulo 145: Utilizando declaración
Introducción
Una declaración de using introduce un nombre único en el alcance actual que se declaró
anteriormente en otro lugar.
Sintaxis
• utilizando typename ( opt ) nested -name-specifier unqualified-id ;
• utilizando :: id no calificado ;
Observaciones
Una declaración de uso es distinta de una directiva de uso , que le dice al compilador que busque
en un espacio de nombres particular cuando busca cualquier nombre. Una directiva de uso
comienza con el using namespace .
Una declaración de uso también es distinta de una declaración de alias, que da un nuevo nombre
a un tipo existente de la misma manera que typedef . Una declaración de alias contiene un signo
igual.
Examples
Importando nombres individualmente desde un espacio de nombres
Una vez que se using uso para introducir el nombre cout del espacio de nombres std en el alcance
de la función main , el objeto std::cout se puede denominar solo cout .
#include <iostream>
int main() {
using std::cout;
cout << "Hello, world!\n";
}
Volver a declarar miembros de una clase base para evitar ocultar el nombre
Si se produce una declaración de uso en el ámbito de la clase, solo se permite volver a declarar a
un miembro de una clase base. Por ejemplo, el using std::cout no está permitido en el alcance de
la clase.
A menudo, el nombre redeclarado es uno que de otra manera estaría oculto. Por ejemplo, en el
código de abajo, d1.foo solo hace referencia a Derived1::foo(const char*) y se producirá un error
de compilación. La función Base::foo(int) está oculta y no se considera en absoluto. Sin
embargo, d2.foo(42) está bien porque la declaración de uso trae Base::foo(int) al conjunto de
https://riptutorial.com/es/home 856
entidades llamadas foo en Derived2 . La búsqueda de nombres luego encuentra ambos foo s y la
resolución de sobrecarga selecciona Base::foo .
struct Base {
void foo(int);
};
struct Derived1 : Base {
void foo(const char*);
};
struct Derived2 : Base {
using Base::foo;
void foo(const char*);
};
int main() {
Derived1 d1;
d1.foo(42); // error
Derived2 d2;
d2.foo(42); // OK
}
Heredando constructores
C ++ 11
Como un caso especial, una declaración de uso en el alcance de la clase puede referirse a los
constructores de una clase base directa. Esos constructores luego son heredados por la clase
derivada y pueden usarse para inicializar la clase derivada.
struct Base {
Base(int x, const char* s);
};
struct Derived1 : Base {
Derived1(int x, const char* s) : Base(x, s) {}
};
struct Derived2 : Base {
using Base::Base;
};
int main() {
Derived1 d1(42, "Hello, world");
Derived2 d2(42, "Hello, world");
}
En el código anterior, tanto Derived1 como Derived2 tienen constructores que envían los
argumentos directamente al constructor correspondiente de Base . Derived1 realiza el reenvío
explícitamente, mientras que Derived2 , utilizando la función C ++ 11 de heredar constructores, lo
hace de manera implícita.
https://riptutorial.com/es/home 857
Capítulo 146: Valor y semántica de referencia
Examples
Copia profunda y soporte de movimiento.
Si un tipo desea tener una semántica de valor, y necesita almacenar objetos que se asignan
dinámicamente, en las operaciones de copia, el tipo deberá asignar nuevas copias de esos
objetos. También debe hacer esto para la copia de la asignación.
Este tipo de copia se llama "copia profunda". Toma efectivamente lo que de otro modo habría sido
la semántica de referencia y lo convierte en semántica de valor:
public:
Value() : array_(new Inner[NUM_INNER]){}
C ++ 11
La semántica de Move permite que un tipo como Value evite copiar realmente los datos a los que
se hace referencia. Si el usuario utiliza el valor de una manera que provoca un movimiento, el
objeto "copiado" se puede dejar vacío de los datos a los que hace referencia:
https://riptutorial.com/es/home 858
public:
Value() : array_(new Inner[NUM_INNER]){}
De hecho, incluso podemos hacer que dicho tipo no se pueda copiar, si queremos prohibir las
copias profundas y al mismo tiempo permitir que el objeto se mueva.
public:
Value() : array_(new Inner[NUM_INNER]){}
https://riptutorial.com/es/home 859
{
//We've stolen the old value.
val.array_ = nullptr;
}
public:
Value() : array_(new Inner[NUM_INNER]){}
Definiciones
Un tipo tiene valor semántico si el estado observable del objeto es funcionalmente distinto de
todos los demás objetos de ese tipo. Esto significa que si copia un objeto, tiene un nuevo objeto y
las modificaciones del nuevo objeto no serán visibles de ninguna manera desde el objeto anterior.
int i = 5;
int j = i; //Copied
j += 20;
std::cout << i; //Prints 5; i is unaffected by changes to j.
La mayoría de los tipos definidos de biblioteca estándar también tienen semántica de valor:
https://riptutorial.com/es/home 860
std::vector<int> v1(5, 12); //array of 5 values, 12 in each.
std::vector<int> v2 = v1; //Copies the vector.
v2[3] = 6; v2[4] = 9;
std::cout << v1[3] << " " << v1[4]; //Writes "12 12", since v1 is unchanged.
Se dice que un tipo tiene semántica de referencia si una instancia de ese tipo puede compartir su
estado observable con otro objeto (externo a él), de modo que la manipulación de un objeto hará
que el estado cambie dentro de otro objeto.
Los punteros de C ++ tienen semántica de valor con respecto a qué objeto apuntan, pero tienen
semántica de referencia con respecto al estado del objeto al que apuntan:
https://riptutorial.com/es/home 861
Capítulo 147: Variables en linea
Introducción
Una variable en línea puede definirse en varias unidades de traducción sin violar la Regla de una
definición . Si se define de forma múltiple, el enlazador combinará todas las definiciones en un
solo objeto en el programa final.
Examples
Definición de un miembro de datos estáticos en la definición de clase
Como caso especial, un miembro de datos estáticos constexpr está implícitamente en línea.
class MyString {
public:
MyString() { /* ... */ }
// ...
static constexpr int max_size = INT_MAX / 2;
};
// in C++14, this definition was required in a single translation unit:
// constexpr int MyString::max_size;
https://riptutorial.com/es/home 862
Creditos
S.
Capítulos Contributors
No
Administracion de
2 Anonymous1847
recursos
https://riptutorial.com/es/home 863
Неудачин
Archivos de
7 RamenChef, VermillionAzure
encabezado
Aritmética de punto
8 Xirema
flotante
Búsqueda de
13 nombre dependiente Fanael, Johannes Schaub - litb
del argumento
Comparaciones lado
19 a lado de ejemplos wasthishelpful
clásicos de C ++
https://riptutorial.com/es/home 864
resueltos a través de
C ++ vs C ++ 11 vs
C ++ 14 vs C ++ 17
Comportamiento
2501, Bo Persson, Brian, Dutow, Jahid, Jarod42, jotik, Justin
21 definido por la
Time, lz96, manlio, Nicol Bolas, Peter
implementación
Comportamiento no
23 AndreiM, Brian, Jarod42, Yakk
especificado
Concurrencia con
24 Andrea Chua, JVApen, Nicol Bolas, Sumurai8
OpenMP
Conversiones de tipo
30 4444, Brian, JVApen, Nikola Vasilev
explícito
https://riptutorial.com/es/home 865
Asignación
33 decltype Ajay
Diseño de tipos de
36 Brian, Justin Time
objetos
Ejemplos de servidor
37 Abhinav Gauniyal
cliente
Entrada / salida
41 Daemon, Nicol Bolas, Владимир Стрелец
básica en c ++
Errores comunes de
43 compilación / Asu, immerhart
enlazador (GCC)
Escriba palabras
44 Brian, Justin Time, Omnifarious, RamenChef
clave
https://riptutorial.com/es/home 866
nombres Combrinck, Jarod42, Jérémy Roy, Johannes Schaub - litb,
Julien-L, JVApen, Nicol Bolas, Null, Rakete1111, randag,
Roland, T.C., tenpercent, Yakk
Especificaciones de
46 Brian
vinculación
Especificadores de
47 clase de Brian, start2learn
almacenamiento
Estructuras de datos
48 Gaurav Sehgal
en C ++
Estructuras de
49 sincronización de didiz, Galik, JVApen
hilos.
Expresiones
52 honk, Jonathan Mee, Justin, JVApen
regulares
Función de C ++
"llamada por valor"
54 Error - Syntactical Remorse, Henkersmann
vs. "llamada por
referencia"
Función de
55 sobrecarga de Johannes Schaub - litb, Kunal Tyagi, RamenChef
plantillas
Funciones de
56 miembro de clase Vijayabhaskarreddy CH, Yakk
constante
https://riptutorial.com/es/home 867
especiales para
miembros
Funciones miembro
60 Justin Time, RamenChef
no estáticas
Generación de
62 Ha., manlio, merlinND, Sumurai8
números aleatorios
Herramientas y
Técnicas de
64 Depuración y Adam Trhon, JVApen, King's jester, Misgevolution
Prevención de
Depuración de C ++
Implementación de
66 patrones de diseño Antonio Barreto, datosh, didiz, Jarod42, JVApen, Nikola Vasilev
en C ++
67 Incompatibilidades C パスカル
Internacionalización
69 John Bargman
en C ++
https://riptutorial.com/es/home 868
Yunès, Johan Lundberg, Johannes Schaub - litb, John Slegers,
JVApen, Loki Astari, Loufylouf, M. Viaz, Mike Dvorkin, Nicol
Bolas, Patryk, Praetorian, Rakete1111, RamenChef, Ryan
Haining, Sergio, Serikov, Snowhawk, teivaz, Yakk, ygram
Literales definidos
75 Brian, Cid1025, Jarod42, Roland, sigalor, sth
por el usuario
Manipuladores de
77 Nicol Bolas, Владимир Стрелец
corriente
Más
78 comportamientos didiz
indefinidos en C ++
Metaprogramacion
81 Meena Alfons
aritmica
Modelo de memoria
82 NonNumeric
C ++ 11
Optimización en C
88 4444, JVApen, lorro, mindriot
++
https://riptutorial.com/es/home 869
Barry, Jarod42, Jatin, Justin, Podgorskiy, tenpercent,
90 palabra clave const
ThyReaper
palabra clave
91 Barry, Community, Dean Seo, start2learn, T.C., tenpercent
mutable
Palabras clave de la
93 Brian, RamenChef, start2learn
declaración variable
Palabras clave de
94 amanuel2, Brian, Kerrek SB, RamenChef
tipo básico
Paquetes de
95 Marco A.
parametros
Patrón de diseño
96 deepmax, Galik, Jarod42, Johan Lundberg, JVApen, Stradigos
Singleton
Patrón de Plantilla
97 Curiosamente Barry, Brian, Gabriel, honk, Nicol Bolas, Ryan Haining
Recurrente (CRTP)
precedencia del
102 an0o0nym, Brian, didiz, JVApen, start2learn, turoni
operador
https://riptutorial.com/es/home 870
Chandler, krOoze, manlio, Marco A., Maxito, n.m., Nicol Bolas,
Peter, phandinhlan, Richard Dally, Sean, signal, silvergasp,
Sumurai8, T.C., Tanjim Hossain, tenpercent, The Philomath,
Владимир Стрелец, パスカル
Pruebas unitarias en
104 elvis.dukaj, VermillionAzure
C ++
Punteros a los
106 John Burger, start2learn
miembros
RAII: la adquisición
Barry, defube, Jarod42, JVApen, Loki Astari, Niall, Nicol Bolas,
108 de recursos es la
RamenChef, Sumurai8, Tannin
inicialización
110 Reenvío perfecto In silico, Johannes Schaub - litb, Nicol Bolas, Roland
Regla de una
112 Brian, Jarod42
definición (ODR)
Resolución de
113 Barry, Brian, didiz, Johannes Schaub - litb
sobrecarga
RTTI: Información de
Brian, deepmax, Pankaj Kumar Boora, Roland, Savas Mikail
114 tipo de tiempo de
KAPLAN
ejecución
Separadores de
116 diegodfrf, JVApen
dígitos
117 SFINAE (el fallo de Barry, Fox, Jarod42, Jason R, Jonathan Lee, Luc Danton,
https://riptutorial.com/es/home 871
sustitución no es un sp2danny, SU3, w1th0utnam3, Xosdy, Yakk
error)
120 static_assert Jarod42, JVApen, lorro, Marco A., Richard Dally, T.C.
std ::
126 Dietmar Kühl
integer_sequence
129 std :: opcional Barry, diegodfrf, Jahid, Jared Payne, JVApen, Null, Yakk
https://riptutorial.com/es/home 872
JPNotADragon, jpo38, Justin, JVApen, Ken Y-N, Leandros,
Loki Astari, manetsus, manlio, Marc.2377, Matthew, Matthieu
M., Meysam, Michael Gaskill, mpromonet, Niall, Null,
Rakete1111, RamenChef, Richard Dally, SajithP, Serikov,
sigalor, Skipper, Soapy, sth, T.C., Tharindu Kumara, Trevor
Hickey, user1336087, user2176127, W.F., Wolf, Yakk
Tipo de Devolución
137 Cheers and hth. - Alf, sorosh_sabz
Covarianza
Typedef y alias de
142 Brian
tipo
https://riptutorial.com/es/home 873
143 Uniones manlio, ThyReaper, txtechhelp
Usando std ::
144 tulak.hord
unordered_map
Utilizando
145 Brian
declaración
Valor y semántica de
146 JVApen, Nicol Bolas
referencia
https://riptutorial.com/es/home 874