std::string_view [C++17] es un objeto ligero que actúa como una “ventana” o vista sobre una secuencia continua de caracteres que ya existe en memoria. A diferencia de std::string, que es un contenedor que gestiona su propia memoria (probablemente en el heap), un string_view no es propietario. Internamente, solo almacena dos elementos: un puntero constante (const char*) al inicio de la secuencia y un tamaño (size_t).
Esta arquitectura permite que cualquier operación de “rebanado” (como substr) sea una operación de coste constante $O(1)$. En lugar de copiar caracteres a un nuevo buffer, simplemente calculamos un nuevo puntero y un nuevo tamaño. Es la herramienta ideal para optimizar funciones que solo necesitan leer texto, permitiéndoles aceptar tanto std::string como literales de cadena (const char*) sin la sobrecarga de crear objetos temporales. Sin embargo, esta eficiencia tiene un precio: la seguridad de la memoria. Si el objeto que “dueño” de los datos (el owner) desaparece o se reasigna, la vista queda invalidada, apuntando a memoria basura.
#include <iostream>
#include <string>
#include <string_view>
#include <vector>
// Recibe una vista. Es mucho más eficiente que const std::string&
// porque evita la creación de un std::string temporal si le pasas un literal.
void analizar_comando(std::string_view comando) {
std::cout << "Analizando: '" << comando << "' (size: " << comando.size() << ")\n";
// C++20: Métodos de inspección directa sin copiar
if (comando.starts_with("CMD_")) {
// C++23: contains() para búsquedas rápidas
if (comando.contains("RESTART")) {
std::cout << " -> Acción: Reiniciando sistema...\n";
} else {
std::cout << " -> Acción: Comando de sistema detectado.\n";
}
} else {
std::cout << " -> Acción: Ignorando comando no reconocido.\n";
}
// substr() es O(1): solo mueve el puntero y el tamaño interno
if (comando.size() > 10) {
std::string_view sub = comando.substr(0, 5);
std::cout << " -> Prefijo extraído: " << sub << "\n";
}
}
int main() {
// 1. Desde un literal (sin asignación en heap)
std::string_view literal = "CMD_RESTART_SYSTEM";
analizar_comando(literal);
// 2. Desde un std::string (la vista apunta a la memoria de 's')
std::string s = "CMD_LOG_DATA";
analizar_comando(s);
// 3. Manipulación de la vista sin tocar el string original
std::string original = " datos_sucios ";
std::string_view vista = original;
vista.remove_prefix(2); // Elimina los dos primeros espacios
vista.remove_suffix(2); // Elimina los dos últimos espacios
std::cout << "Original: '" << original << "' | Vista limpia: '" << vista << "'\n";
// Cuidado con data(): no garantiza terminación nula si se ha recortado
std::string_view recorte = vista.substr(0, 4);
// Si pasaras recorte.data() a una función de C como printf("%s"),
// leería más allá de 'datos' hasta encontrar un '\0' en la memoria.
std::cout << "Recorte (seguro con string_view): " << recorte << "\n";
std::cout << "Recorte (peligro con printf): " << std::string(recorte) << "\n";
return 0;
}
Análisis del código
En la función analizar_comando, el parámetro std::string_view comando es la clave de la eficiencia. Si hubiéramos usado const std::string&, al llamar a analizar_comando("CMD_RESTART_SYSTEM"), el compilador tendría que instanciar un objeto std::string temporal, lo que implica una posible asignación en el heap. Con string_view, simplemente pasamos un puntero y un tamaño.
Cuando ejecutamos comando.substr(0, 5), no estamos creando una nueva cadena. Internamente, el objeto resultante tiene un puntero que apunta a la misma dirección que comando, pero su campo size es 5. Esto es lo que permite que el rendimiento sea constante independientemente de si el string tiene 10 o 10 millones de caracteres.
En main, observa cómo vista.remove_prefix(2) no modifica la variable original. Solo altera el puntero interno de vista. Esto es fundamental para procesar buffers de datos (como paquetes de red) de forma extremadamente rápida, desplazando el puntero hacia adelante para saltar cabeceras sin mover ni un solo byte de la memoria original.
El error frecuente
El error más crítico es el dangling string_view. Ocurre cuando una vista intenta sobrevivir al objeto que la contiene. Esto suele suceder al devolver un string_view de una función que crea una std::string local.
// ERROR: UB (Undefined Behavior)
std::string_view obtener_nombre_error() {
std::string nombre = "Usuario_Temporal";
return std::string_view(nombre); // Error: 'nombre' se destruye al salir de la función
}
int main() {
std::string_view sv = obtener_nombre_error();
std::cout << sv << std::endl; // Crasheo o basura: el string original ya no existe
}
Si intentas compilar esto con AddressSanitizer (-fsanitize=address), el programa lanzará un error de use-after-scope inmediatamente. Nunca almacenes un string_view en una estructura de datos persistente (como un std::vector o una clase miembro) a menos que el ciclo de vida del dueño de los datos sea mayor o igual al de la vista.
Para la mayoría de los casos de uso, la regla de oro es: usa std::string_view para pasar datos hacia abajo en la jerarquía de llamadas, pero usa std::string para almacenar datos en la jerarquía de objetos.
N° 78