C ++ Devolver referencia a la variable local

116

¿Es correcto el siguiente código (func1 ()) si tiene que devolver i? Recuerdo haber leído en alguna parte que hay un problema al devolver la referencia a una variable local. ¿En qué se diferencia de func2 ()?

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

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}
blitzkriegz
fuente
1
Si cambia func1 () para usar memoria asignada dinámicamente, entonces son los mismos :-)int& i = * new int;
Martin York
1
Relacionado para los locales const: stackoverflow.com/questions/2784262/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

193

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 que una vez que func1()regresa, int imuere, 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 deletede realizar las asignaciones 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
}

Normalmente, envolvería el puntero en alguna clase RAII y / o una función de fábrica para no tener quedelete hacerlo usted mismo.

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

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 que func3()devuelve valores primitivos porque casi todos los compiladores implementan actualmente 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 una referencia constante es C ++ perfectamente legal .

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();  
}
En silico
fuente
2
Hermosa explicación. : hattip: En el tercer fragmento de código, estás eliminando. int* p = func2(); delete p;Ahora, cuando eliminaste 'p', ¿significa que la memoria asignada "dentro" de la func2()definición de la función también se eliminó?
Aquarius_Girl
2
@Anisha Kaul: Sí. La memoria se asignó adentro func2()y se liberó 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 elegir un buen libro de introducción a 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.
In silico
Ahora lo 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. Ahora está claro y gracias por el enlace.
Aquarius_Girl
y has editado la respuesta ?? : enojado: Podría haberlo perdido fácilmente. ;);)
Aquarius_Girl
@Anisha Kaul: No, no lo hice. La última vez que edité mi respuesta fue el 10 de enero, según la marca de tiempo debajo de mi publicación.
In silico
18

Una variable local es la memoria en la pila, esa memoria no se invalida automáticamente cuando se sale del alcance. Desde una función anidada más profundamente (más arriba en la pila en la memoria), es perfectamente seguro acceder a esta memoria.

Sin embargo, una vez que la función regresa y termina, 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 todavía contiene sus datos; el puntero parece válido.

Hasta que otra función acumula la pila y la sobrescribe. Esta es la razón por la que esto puede funcionar por un tiempo, y luego dejar de funcionar repentinamente después de que un conjunto de funciones particularmente anidado profundamente, o una función con un tamaño realmente grande o muchos objetos locales, alcance 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 debería desalentarse en gran medida. ¡No use punteros a objetos locales!

Pica
fuente
2

Algo bueno para recordar son estas reglas simples, y se aplican tanto a parámetros como a tipos de retorno ...

  • Valor: realiza una copia del artículo 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 vivas localmente en el ámbito de la función. En su ejemplo, tener un tipo de int*devolución y devolución &ihabría sido igualmente incorrecto. Estaría mejor en ese caso haciendo esto ...

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

Hacerlo cambiaría directamente el valor de su parámetro pasado. Considerando que este código ...

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

no lo haría. Simplemente cambiaría el valor de oValuelocal a la llamada a la función. La razón de esto es porque en realidad solo cambiaría una copia "local" de oValue, y no a oValuesí mismo.

David Sumich
fuente