std::string: Gestión dinámica de texto en C++

std::string es un contenedor de la biblioteca estándar diseñado para gestionar secuencias dinámicas de caracteres. A diferencia de los arrays de caracteres de C (char[]), esta clase se encarga automáticamente de la asignación y liberación de memoria en el heap. Una de sus optimizaciones más críticas es el Small String Optimization (SSO): cuando el texto es corto (típicamente menos de 22 caracteres), std::string no realiza una asignación en el heap, sino que almacena los caracteres directamente en un búfer interno dentro del propio objeto en el stack. Esto evita una operación costosa de gestión de memoria para textos pequeños.

Deberás usar std::string siempre que necesites manipular texto, concatenar palabras o manejar cadenas de longitud desconocida en tiempo de compilación. Si intentas acceder a una posición fuera de los límites usando el operador [], entrarás en comportamiento indefinido (UB), lo que significa que el programa podría funcionar erróneamente, corromper datos o cerrarse abruptamente sin dar explicaciones.

#include <iostream>
#include <string>
#include <string_view> // [C++17]

int main() {
    // 1. Construcción
    std::string saludo = "Hola"; // Desde un literal
    std::string_view vista = "C++20"; // [C++17]
    std::string mensaje{vista};     // Constructor desde string_view
    
    // 2. Modificación y concatenación
    mensaje += ", ";           // Operador += para append
    mensaje.append("desarrollador");
    mensaje.insert(5, " pro"); // Inserta " pro" en la posición 5
    
    std::cout << "Mensaje completo: " << mensaje << "\n";

    // 3. Búsqueda y subcadenas
    std::string buscar = "desarrollador";
    size_t posicion = mensaje.find(buscar); // Retorna std::string::npos si no lo encuentra

    if (posicion != std::string::npos) {
        std::string sub = mensaje.substr(posicion, 12); // substr(inicio, longitud)
        std::cout << "Subcadena encontrada: " << sub << "\n";
    }

    // 4. Gestión de memoria y capacidad
    // Usamos reserve para evitar múltiples reasignaciones en el heap
    std::string buffer;
    buffer.reserve(100); // Reserva espacio para 100 caracteres sin cambiar su size()

    for (int i = 0; i < 5; ++i) {
        buffer += "item "; 
    }

    // 5. Acceso seguro vs rápido
    try {
        char c = buffer.at(5); // Acceso seguro: lanza std::out_of_range si está fuera de límites
        char c_rapido = buffer[2]; // Acceso rápido: sin comprobaciones (más eficiente)
        
        std::cout << "Buffer: " << buffer << " (Capacidad: " << buffer.capacity() << ")\n";
    } catch (const std::out_of_range& e) {
        std::cerr << "Error de acceso: " << e.what() << "\n";
    }

    // 6. Interoperabilidad con C
    const char* c_str = mensaje.c_str(); // Retorna puntero null-terminated compatible con C
    std::cout << "Puntero C: " << c_str << "\n";

    return 0;
}

Vamos a analizar lo que está ocurriendo en el código anterior:

Al declarar saludo, el compilador utiliza el constructor desde un literal de cadena. Como “Hola” es muy corta, el SSO evita la asignación en el heap. Sin embargo, cuando modificamos mensaje con append e insert, la cadena crece; si supera la capacidad actual, std::string reserva un nuevo bloque de memoria más grande, copia los elementos y libera el anterior.

Fíjate en la importancia de reserve(100). Al llamar a buffer.reserve(100), estamos ajustando la capacity() de buffer a 100, pero su size() sigue siendo 0. Esto es vital para el rendimiento: si hiciéramos el bucle for sin reserve, el objeto podría tener que reasignar memoria y copiar los datos varias veces a medida que el búfer se llena. Con reserve, garantizamos que solo habrá una asignación inicial.

En la búsqueda, mensaje.find(buscar) nos devuelve un índice. Si no existiera, devolvería std::string::npos, que es una constante que representa el valor máximo de size_t, utilizada para indicar que la búsqueda falló.

Respecto al acceso a caracteres, buffer[2] es una operación de costo constante ($O(1)$) que simplemente calcula la dirección de memoria (puntero base + índice). Es la opción preferida en bucles de alto rendimiento. Por el contrario, buffer.at(5) realiza una comprobación extra de límites; si el índice fuera inválido, lanza una excepción std::out_of_range.

Finalmente, mensaje.c_str() nos devuelve un const char*. Desde [C++11], la biblioteca garantiza que los datos de std::string están almacenados de forma contigua en memoria y terminados con un carácter nulo (\0), lo que permite pasar este puntero a funciones de librerías de C sin problemas de compatibilidad.

El error frecuente
Un error común es confiar en el operador [] para acceder a elementos en bucles, asumiendo que el programa fallará de forma controlada si el índice es incorrecto.

std::string texto = "C++";
// ERROR: El índice 10 está fuera de los límites (size es 3)
char error = texto[10]; 
std::cout << error << std::endl;

En este caso, no hay una excepción que detener el programa. El programa intentará leer la memoria en la dirección base_de_texto + 10. Esto puede resultar en un “crash” inmediato (Segmentation Fault) o, lo que es más peligroso, el programa puede imprimir basura de la memoria y continuar ejecutándose como si nada, introduciendo errores lógicos impredecibles. Si utilizas -fsanitize=undefined en gcc o clang, el UBSan (Undefined Behavior Sanitizer) detectará este acceso ilegal durante las pruebas.

77

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio