HOWTO · C++

Regresar el puntero a la array en C++

Este artículo demuestra cómo devolver un puntero a una array desde la función en C++

En esta página

Una función de C++ puede devolver un puntero a un elemento de un array, pero el puntero no contiene la longitud del array ni indica quién es su propietario. Para usarlo de forma segura, hay que saber qué objeto es dueño del almacenamiento, cuántos elementos hay disponibles y durante cuánto tiempo permanece vivo ese objeto. Para resultados habituales, suele ser más claro devolver un contenedor por valor que transferir un puntero sin propietario.

Entender a qué apunta un puntero a un array

En un parámetro de función, int values[] se ajusta a int* values. No conserva un límite fijo para el array, así que hay que pasar el número de elementos por separado o usar un contenedor que incluya su tamaño. El puntero apunta al primer elemento; expresiones de índice como values[i] acceden a los elementos siguientes solo si pertenecen al mismo array y el índice está dentro de sus límites.

Devolver values devuelve esa dirección. No copia el array ni prolonga su vida útil. Para un objeto std::array, como values, llamar a su miembro values.data() devuelve un puntero a sus elementos. Ese puntero solo sigue siendo válido mientras el objeto values permanezca vivo y su almacenamiento no haya quedado invalidado.

Devolver un puntero a un array propiedad de quien llama

Un patrón útil con punteros sin propietario consiste en dejar que quien llama sea dueño del array y pasar a la función su dirección y longitud. La función puede modificar los elementos y devolver el mismo puntero para que quien llama pueda seguir usándolo cómodamente. Quien llama debe mantener vivo el array y conservar intacto su almacenamiento mientras use el puntero devuelto.

Ejecuta este ejemplo:

El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.

#include <cstddef>
#include <iostream>

int* subtractFrom(int values[], std::size_t size, int amount) {
    for (std::size_t i = 0; i < size; ++i) {
        values[i] -= amount;
    }
    return values;
}

int main() {
    int values[] = {5, 10, 15};
    int* result = subtractFrom(values, 3, 2);

    for (std::size_t i = 0; i < 3; ++i) {
        if (i != 0) std::cout << ' ';
        std::cout << result[i];
    }
    std::cout << '\n';
}
3 8 13

El array pertenece a main, así que sigue vivo mientras allí se lee result. El tamaño se proporciona por separado porque el parámetro de la función no conoce la longitud original del array. Para un contenedor de tamaño fijo, std::array<int, 3> values ofrece un objeto cuyo tamaño está disponible; su miembro data() puede proporcionar un puntero a sus elementos a una interfaz que lo necesite.

Devolver un puntero al almacenamiento de un array asignado dinámicamente

Una función puede asignar almacenamiento con new[] y devolver el puntero resultante. Esto transfiere a quien llama la responsabilidad de liberar manualmente la memoria: debe conservar el puntero, no usar más elementos que los asignados y liberar la asignación exactamente una vez con delete[] cuando termine. También debe conocer la cantidad de elementos, ya que un puntero sin propietario no la registra. Combinar new[] con delete escalar, liberar la memoria dos veces u olvidarse de liberarla provoca errores o fugas.

En este ejemplo, makeSequence asigna almacenamiento y lo llena con tres enteros. Quien llama recibe el puntero, usa la cantidad de elementos conocida y libera el array con el delete[] correspondiente:

El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.

#include <cstddef>
#include <iostream>

int* makeSequence(std::size_t count) {
    int* values = new int[count];
    for (std::size_t i = 0; i < count; ++i) {
        values[i] = static_cast<int>(i * 5 + 3);
    }
    return values;
}

int main() {
    const std::size_t count = 3;
    int* values = makeSequence(count);

    for (std::size_t i = 0; i < count; ++i) {
        std::cout << values[i] << '\n';
    }

    delete[] values;
}

Esta técnica permite representar una cantidad de elementos elegida durante la ejecución, pero es fácil perder de vista quién es dueño del almacenamiento. Para un resultado de longitud variable, es preferible devolver std::vector<int> por valor. El vector gestiona su propia asignación, comunica su tamaño y puede moverse de manera eficiente; quien llama solo debe usar sus elementos mientras el vector siga vivo y su almacenamiento no haya sido invalidado por una operación que lo reasigne.

Evitar los punteros colgantes y elegir un tipo de retorno más seguro

No devuelvas un puntero a un array local automático. Su vida útil termina cuando retorna la función, por lo que quien llama se queda con un puntero colgante. Esto difiere de un array static local a la función: el almacenamiento estático persiste durante toda la ejecución del programa, así que el puntero no queda colgante solo porque la función haya retornado. Sin embargo, las llamadas comparten el mismo array mutable, su capacidad es fija y los usos concurrentes o anidados pueden interferir entre sí. Usa almacenamiento estático solo si el estado compartido es intencional.

Para un resultado de tamaño fijo, devuelve std::array<int, N> por valor. Para un resultado de tamaño variable, devuelve std::vector<int> por valor. Estos tipos hacen explícita la propiedad y evitan que quien llama tenga que emparejar un puntero sin propietario con una regla de liberación aparte. Si una API debe devolver un puntero a un elemento, documenta qué objeto es su propietario y durante cuánto tiempo es válido; no uses el puntero después de que su propietario se destruya o cambie su almacenamiento.