Biblioteca estándar de C++ utility
En la biblioteca estándar de C++,<utility>El archivo de cabecera contiene clases y funciones de utilidad prácticas, que son muy útiles al escribir código eficiente y legible.
utilityEl valor principal de la biblioteca radica en:
- Proporcionar estructuras de datos básicas y funciones de utilidad
- Simplificar la implementación de tareas de programación comunes
- Proporcionar soporte básico para otros componentes de la biblioteca estándar
utilityEl archivo de cabecera es pequeño, pero las herramientas que proporciona son muy prácticas:
| Componente/Función | Uso | Escenario de uso |
|---|---|---|
std::pair |
Almacenar dos valores relacionados | Funciones que devuelven múltiples valores, elementos de map |
std::make_pair |
Creación conveniente de pair | Deducción de tipos en plantillas, simplificación de código |
std::swap |
Intercambiar dos valores | Implementación de algoritmos, operaciones de ordenamiento |
std::move |
Habilitar la semántica de movimiento | Gestión de recursos, optimización del rendimiento |
std::forward |
Reenvío perfecto | Referencias universales, programación con plantillas |
Explicación detallada de los componentes principales
std::pair: contenedor de pares clave-valor
std::pairSíutilityEl componente más utilizado de la biblioteca, se usa para combinar dos valores en un solo objeto.
Sintaxis básica
#include <utility> // 创建 pair 对象的基本方式 std::pair<类型1, 类型2> 变量名(值1, 值2);
Ejemplo
#include <utility>
#include <string>
int main() {
// Método 1: inicialización directa
std::pair<int, std::string> student1(101, "Alice");
// Método 2: usar la función make_pair (recomendado)
auto student2 = std::make_pair(102, "Bob");
// Método 3: guía de deducción compatible desde C++17
std::pair student3(103, "Charlie");
// Acceder a los miembros de pair
std::cout << "ID de estudiante: " << student1.first << ", Nombre: " << student1.second << std::endl;
return 0;
}
Operaciones comunes de pair
Ejemplo
#include <iostream>
void pairOperations() {
// Crear un pair
std::pair<int, double> p1(10, 3.14);
std::pair<int, double> p2(20, 2.71);
// Operaciones de comparación
if (p1 < p2) {
std::cout << "p1 es menor que p2" << std::endl;
}
// Operación de asignación
p1 = p2;
// Asignación miembro a miembro compatible desde C++11
int a;
double b;
std::tie(a, b) = p1; // Asignar los valores de p1 a a y b respectivamente
// Enlace estructurado de C++17 (más conciso)
auto [x, y] = p1;
std::cout << "x = " << x << ", y = " << y << std::endl;
}
std::make_pair: función de creación conveniente
std::make_pairEs una función de plantilla que puede deducir tipos automáticamente, simplificando el proceso de creación de pair.
Ejemplo
#include <iostream>
#include <string>
void demonstrateMakePair() {
// Deducción automática de tipos, sin necesidad de especificar explícitamente los parámetros de plantilla
auto p1 = std::make_pair(42, "Hello");
auto p2 = std::make_pair(3.14, true);
// Es especialmente conveniente usarlo en contenedores
std::vector<std::pair<int, std::string>> students;
students.push_back(std::make_pair(101, "Alice"));
students.push_back(std::make_pair(102, "Bob"));
for (const auto& student : students) {
std::cout << "ID de estudiante: " << student.first
<< ", Nombre: " << student.second << std::endl;
}
}
Funciones de utilidad
std::swap: intercambia dos valores
std::swapSe utiliza para intercambiar los valores de dos objetos del mismo tipo.
Ejemplo
#include <iostream>
void demonstrateSwap() {
int a = 10, b = 20;
std::cout << "Antes del intercambio: a = " << a << ", b = " << b << std::endl;
std::swap(a, b);
std::cout << "Después del intercambio: a = " << a << ", b = " << b << std::endl;
// También se puede usar con tipos personalizados (si implementan semántica de movimiento)
std::string str1 = "Hello", str2 = "World";
std::swap(str1, str2);
std::cout << "Intercambio de cadenas: " << str1 << " " << str2 << std::endl;
}
std::move: soporte de semántica de movimiento
std::moveSe utiliza para convertir un objeto en una referencia rvalue, habilitando la semántica de movimiento.
Ejemplo
#include <iostream>
#include <vector>
void demonstrateMove() {
std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2;
std::cout << "Antes de mover - tamaño de v1: " << v1.size()
<< ", tamaño de v2: " << v2.size() << std::endl;
// Transferir la propiedad de los recursos usando semántica de movimiento
v2 = std::move(v1);
std::cout << "Después de mover - tamaño de v1: " << v1.size()
<< ", tamaño de v2: " << v2.size() << std::endl;
// v1 ahora está en un estado válido pero no definido
// Normalmente no se debe volver a usar v1, a menos que se reasigne
}
std::forward: reenvío perfecto
std::forwardSe utiliza para implementar el reenvío perfecto, manteniendo la categoría de valor de los parámetros.
Ejemplo
#include <iostream>
// Función normal: no puede mantener la categoría de valor
template<typename T>
void normalFunction(T arg) {
std::cout << "Parámetro de función normal" << std::endl;
}
// Función que utiliza reenvío perfecto
template<typename T>
void perfectForwardingFunction(T&& arg) {
// Mantiene la categoría de valor original del parámetro (lvalue o rvalue)
normalFunction(std::forward<T>(arg));
}
void demonstrateForward() {
int x = 10;
// Pasar un lvalue
perfectForwardingFunction(x);
// Pasar un rvalue
perfectForwardingFunction(20);
}
Herramientas de secuencias de enteros (C++14)
C++14 introdujo herramientas relacionadas con secuencias de enteros, utilizadas principalmente en metaprogramación con plantillas.
std::integer_sequence
Ejemplo
#include <iostream>
// Usar una secuencia de enteros para imprimir cada valor de la secuencia
template<typename T, T... Ints>
void print_sequence(std::integer_sequence<T, Ints...>) {
// Usar expresiones de plegado (C++17) para imprimir todos los valores
((std::cout << Ints << " "), ...);
std::cout << std::endl;
}
void demonstrateIntegerSequence() {
// Crear una secuencia de enteros
auto seq = std::integer_sequence<int, 1, 2, 3, 4, 5>();
print_sequence(seq);
// Usar make_integer_sequence para generar la secuencia
auto seq2 = std::make_integer_sequence<int, 5>();
print_sequence(seq2); // Salida: 0 1 2 3 4
}
Casos de aplicación práctica
Caso 1: función que devuelve múltiples valores
Ejemplo
#include <iostream>
#include <cmath>
// La función devuelve múltiples valores: resultado del cálculo y código de error
std::pair<double, bool> calculateSqrt(double number) {
if (number < 0) {
return std::make_pair(0.0, false); // Caso de error
}
return std::make_pair(std::sqrt(number), true); // Caso de éxito
}
void multipleReturnValues() {
auto result1 = calculateSqrt(16.0);
if (result1.second) {
std::cout << "Raíz cuadrada: " << result1.first << std::endl;
} else {
std::cout << "Error de cálculo: no se puede calcular la raíz cuadrada de un número negativo" << std::endl;
}
auto result2 = calculateSqrt(-4.0);
if (!result2.second) {
std::cout << "Error de cálculo: no se puede calcular la raíz cuadrada de un número negativo" << std::endl;
}
}
Caso 2: aplicación en contenedores STL
Ejemplo
#include <map>
#include <iostream>
#include <string>
void mapWithPair() {
// Cada elemento de std::map es un std::pair
std::map<int, std::string> studentMap;
// Insertar un par clave-valor
studentMap.insert(std::make_pair(101, "Alice"));
studentMap.emplace(102, "Bob"); // Forma más eficiente
// Recorrer el map
for (const auto& [id, name] : studentMap) {
std::cout << "ID de estudiante: " << id << ", Nombre: " << name << std::endl;
}
// Buscar un elemento
auto it = studentMap.find(101);
if (it != studentMap.end()) {
std::cout << "Estudiante encontrado: " << it->second << std::endl;
}
}
Caso 3: implementación de un diccionario simple
Ejemplo
#include <vector>
#include <iostream>
#include <algorithm>
class SimpleDictionary {
private:
std::vector<std::pair<std::string, std::string>> entries;
public:
void addWord(const std::string& word, const std::string& meaning) {
entries.emplace_back(word, meaning);
}
std::pair<bool, std::string> findMeaning(const std::string& word) {
for (const auto& [w, m] : entries) {
if (w == word) {
return std::make_pair(true, m);
}
}
return std::make_pair(false, "");
}
void printAll() {
for (const auto& [word, meaning] : entries) {
std::cout << word << ": " << meaning << std::endl;
}
}
};
void dictionaryExample() {
SimpleDictionary dict;
dict.addWord("apple", "Una fruta");
dict.addWord("book", "Objeto utilizado para leer");
auto result = dict.findMeaning("apple");
if (result.first) {
std::cout << "Significado: " << result.second << std::endl;
}
}
Mejores prácticas y precauciones
1. Usar auto para simplificar la creación de pair
Ejemplo
auto student = std::make_pair(101, "Alice");
// No recomendado: especificar explícitamente los tipos (más verboso)
std::pair<int, std::string> student(101, "Alice");
2. Preferir emplace en lugar de insert
Ejemplo
// Recomendado: usar emplace (más eficiente)
myMap.emplace(1, "one");
// No recomendado: usar insert (requiere construir objetos temporales)
myMap.insert(std::make_pair(1, "one"));
3. Uso correcto de la semántica de movimiento
Ejemplo
std::string str = "Cadena muy grande";
// Correcto: usar movimiento al devolver
return str; // El compilador optimiza automáticamente, sin necesidad de move explícito
}
void processString(std::string str) {
// Procesar la cadena
}
void usageExample() {
std::string largeStr = "Datos muy grandes";
// Correcto: usar movimiento al pasar
processString(std::move(largeStr));
// Nota: largeStr no debe usarse más
}
4. Uso de enlaces estructurados
Ejemplo
std::pair<int, std::string> getStudent() {
return {101, "Alice"};
}
void structuredBindingExample() {
// Forma tradicional
auto student = getStudent();
int id = student.first;
std::string name = student.second;
// Forma moderna (C++17)
auto [id2, name2] = getStudent(); // Más conciso y claro
}