Devolver una referencia a una variable local en C++

5 minutos de lectura

avatar de usuario de blitzkriegz
guerra relámpago

Es el siguiente código (función1()) correcto si tiene que volver i? Recuerdo haber leído en alguna parte que hay un problema al devolver una referencia a una variable local. ¿Cómo es diferente de función2()?

int& func1()
{
    int i;
    i = 1;
    return i;
}

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

  • Si cambia func1 () para usar memoria asignada dinámicamente, entonces son lo mismo 🙂 int& i = * new int;

    – Martín York

    10 de enero de 2011 a las 4:59


  • Relacionado para locales constantes: stackoverflow.com/questions/2784262/…

    – Ciro Santilli OurBigBook.com

    3 de noviembre de 2015 a las 16:31

Avatar de usuario de In silico
en silico

Este fragmento de código:

int& func1()
{
    int i;
    i = 1;
    return i;
}

no funcionará porque está devolviendo un alias (una referencia) a un objeto con una duración limitada al alcance de la llamada a la función. Eso significa una vez func1() devoluciones, int i muere, lo que hace que la referencia devuelta por la función no tenga valor porque ahora se refiere a un objeto que no existe.

int main()
{
    int& p = func1();
    /* p is garbage */
}

La segunda versión funciona porque la variable se asigna en la tienda gratuita, que no está vinculada a la duración de la llamada a la función. Sin embargo, usted es responsable de deleteing el asignado int.

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

int main()
{
    int* p = func2();
    /* pointee still exists */
    delete p; // get rid of it
}

Por lo general, envolvería el puntero en algún RAI clase y/o una función de fábrica para que no tenga que delete tú mismo

En cualquier caso, puede devolver el valor en sí mismo (aunque me doy cuenta de que el ejemplo que proporcionó probablemente fue inventado):

int func3()
{
    return 1;
}

int main()
{
    int v = func3();
    // do whatever you want with the returned value
}

Tenga en cuenta que está perfectamente bien devolver objetos grandes de la misma manera func3() devuelve valores primitivos porque casi todos los compiladores hoy en día implementan alguna forma de optimización del valor de retorno:

class big_object 
{ 
public:
    big_object(/* constructor arguments */);
    ~big_object();
    big_object(const big_object& rhs);
    big_object& operator=(const big_object& rhs);
    /* public methods */
private:
    /* data members */
};

big_object func4()
{
    return big_object(/* constructor arguments */);
}

int main()
{
     // no copy is actually made, if your compiler supports RVO
    big_object o = func4();    
}

Curiosamente, vincular un temporal a un constante la referencia es perfectamente legal C++.

int main()
{
    // This works! The returned temporary will last as long as the reference exists
    const big_object& o = func4();    
    // This does *not* work! It's not legal C++ because reference is not const.
    // big_object& o = func4();  
}

  • Hermosa explicación. :hattip: En el tercer fragmento de código, está eliminando int* p = func2(); delete p; Ahora, cuando eliminó ‘p’, ¿significa que la memoria asignada “dentro” de la función func2() ¿La definición de también se eliminó?

    – Chica_Acuario

    27 de agosto de 2011 a las 9:24

  • @Anisha Kaul: Sí. La memoria se asignó dentro func2() y liberado afuera en la siguiente línea. Sin embargo, es una forma bastante propensa a errores de manejar la memoria, como dije, usaría alguna variante de RAII en su lugar. Por cierto, parece que estás aprendiendo C++. Recomiendo escoger un buen libro introductorio de C++ para aprender. Además, para referencia futura si tiene una pregunta, siempre puede publicar la pregunta en Stack Overflow. Los comentarios no están destinados a hacer preguntas totalmente nuevas.

    – En silicio

    27 de agosto de 2011 a las 9:34


  • Ahora entendí, ¡lo has hecho bien! La función devolvía un puntero, y fuera de esa función, ha eliminado la memoria a la que apuntaba. Ya está claro, y gracias por el enlace.

    – Chica_Acuario

    27 de agosto de 2011 a las 9:38

  • y has editado la respuesta?? 😡 Podría haberlo perdido fácilmente. 😉 😉

    – Chica_Acuario

    27 de agosto de 2011 a las 10:05

  • @Anisha Kaul: No, no lo hice. La última vez que edité mi respuesta fue el 10 de enero, de acuerdo con la marca de tiempo debajo de mi publicación.

    – En silicio

    27 de agosto de 2011 a las 10:18


Avatar de usuario de Pica
Pica

Una variable local es memoria en la pila, y esa memoria no se invalida automáticamente cuando sale del alcance. A partir de una función anidado más profundo (más alto en la pila en la memoria), es perfectamente seguro acceder a esta memoria.

Sin embargo, una vez que la función regresa y finaliza, las cosas se ponen peligrosas. Por lo general, la memoria no se elimina ni se sobrescribe cuando regresa, lo que significa que la memoria en esa dirección aún contiene sus datos; el puntero parece válido.

Hasta que otra función construya la pila y la sobrescriba. Es por eso que esto puede funcionar por un tiempo, y luego dejar de funcionar repentinamente después de que un conjunto de funciones anidadas particularmente profundas, o una función con muchos objetos locales o de gran tamaño, alcanza esa memoria de pila nuevamente.

Incluso puede suceder que vuelva a llegar a la misma parte del programa y sobrescriba su antigua variable de función local con la nueva variable de función. Todo esto es muy peligroso y debe ser fuertemente desaconsejado.

¡No utilice punteros a objetos locales!

Avatar de usuario de David Sumich
David Sumich

Es bueno recordar estas reglas simples, y se aplican tanto a los parámetros como a los tipos de devolución…

  • Valor: hace una copia del elemento en cuestión.
  • Puntero – se refiere a la dirección del artículo en cuestión.
  • Referencia: es literalmente el elemento en cuestión.

Hay un momento y un lugar para cada uno, así que asegúrese de conocerlos. Las variables locales, como ha mostrado aquí, son solo eso, limitadas al tiempo que están activas localmente en el ámbito de la función. En su ejemplo, tener un tipo de retorno de int* y regresando &i habría sido igualmente incorrecto. Sería mejor en ese caso hacer esto…

void func1(int& oValue)
{
    oValue = 1;
}

Si lo hace, cambiaría directamente el valor de su parámetro pasado. Mientras que este código…

void func1(int oValue)
{
    oValue = 1;
}

no lo haría Simplemente cambiaría el valor de oValue local a la llamada de función. La razón de esto es que en realidad estaría cambiando solo una copia “local” de oValuey no oValue sí mismo.

¿Ha sido útil esta solución?