C ++ Retornando referência à variável local


117

O código a seguir (func1 ()) está correto se tiver que retornar i? Lembro-me de ter lido em algum lugar que há um problema ao retornar a referência a uma variável local. Como é diferente de func2 ()?

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

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

1
Se você alterar func1 () para usar memória alocada dinamicamente, eles serão os mesmos :-)int& i = * new int;
Martin York

Respostas:


193

Este snippet de código:

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

não funcionará porque você está retornando um alias (uma referência) para um objeto com uma vida útil limitada ao escopo da chamada de função. Isso significa que uma vez que func1()retorna, int imorre, tornando a referência retornada da função inútil porque agora se refere a um objeto que não existe.

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

A segunda versão funciona porque a variável é alocada no armazenamento gratuito, o que não é limitado ao tempo de vida da chamada de função. No entanto, você é responsável por deletefazer o envio 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, você envolveria o ponteiro em alguma classe RAII e / ou uma função de fábrica para que não precisasse fazer deleteisso sozinho.

Em qualquer caso, você pode simplesmente retornar o valor em si (embora eu saiba que o exemplo que você forneceu provavelmente foi inventado):

int func3()
{
    return 1;
}

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

Observe que é perfeitamente normal retornar objetos grandes da mesma forma que func3()retorna valores primitivos porque quase todo compilador hoje em dia implementa alguma forma de otimização de 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 um temporário a uma referência const é perfeitamente C ++ 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();  
}

2
Bela explicação. : hattip: No terceiro trecho de código, você está excluindo int* p = func2(); delete p;Agora, quando você excluiu 'p', isso significa que a memória alocada "dentro" da func2()definição da função também foi excluída?
Aquarius_Girl

2
@Anisha Kaul: Sim. A memória foi alocada internamente func2()e liberada externamente na próxima linha. No entanto, é uma forma bastante sujeita a erros de lidar com a memória, como eu disse que você usaria alguma variante do RAII. A propósito, você parece estar aprendendo C ++. Eu recomendo pegar um bom livro introdutório ao C ++ para aprender. Além disso, para referência futura, se você tiver alguma dúvida, poderá postá-la no Stack Overflow. Os comentários não são feitos para fazer perguntas totalmente novas.
In silico

Agora eu entendi, você fez certo! A função estava retornando um ponteiro e, fora dessa função, você excluiu a memória para a qual ele estava apontando. Está claro agora, e obrigado pelo link.
Aquarius_Girl

e você editou a resposta ?? : mad: Eu poderia ter perdido facilmente. ;);)
Aquarius_Girl

@Anisha Kaul: Não, não disse. A última vez que editei minha resposta foi em 10 de janeiro, de acordo com a data e hora em minha postagem.
In silico

18

Uma variável local é a memória na pilha, essa memória não é automaticamente invalidada quando você sai do escopo. De uma função mais aninhada (mais acima na pilha de memória), é perfeitamente seguro acessar essa memória.

Uma vez que a função retorna e termina, as coisas ficam perigosas. Normalmente, a memória não é excluída ou substituída quando você retorna, o que significa que a memória naquele endereço ainda contém seus dados - o ponteiro parece válido.

Até que outra função acumule a pilha e a substitua. É por isso que isso pode funcionar por um tempo - e então parar repentinamente de funcionar depois que um conjunto de funções particularmente profundamente aninhado, ou uma função com um tamanho realmente grande ou muitos objetos locais, atinge aquela pilha de memória novamente.

Pode até acontecer que você alcance a mesma parte do programa novamente e sobrescreva sua antiga variável de função local com a nova variável de função. Tudo isso é muito perigoso e deve ser fortemente desencorajado. Não use ponteiros para objetos locais!


2

É bom lembrar essas regras simples, e elas se aplicam a parâmetros e tipos de retorno ...

  • Valor - faz uma cópia do item em questão.
  • Ponteiro - refere-se ao endereço do item em questão.
  • Referência - é literalmente o item em questão.

Há uma hora e um lugar para cada um, portanto, certifique-se de conhecê-los. As variáveis ​​locais, como você mostrou aqui, são apenas isso, limitadas ao tempo em que estão ativas localmente no escopo da função. Em seu exemplo, ter um tipo de retorno de int*e retornar &iteria sido igualmente incorreto. Você estaria melhor nesse caso fazendo isso ...

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

Fazer isso alteraria diretamente o valor do parâmetro passado. Considerando que este código ...

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

não faria. Isso apenas mudaria o valor de oValuelocal para a chamada de função. A razão para isso é porque você na verdade estaria alterando apenas uma cópia "local" oValue, e não oValueela mesma.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.