[Resolvido] Retorno de referencia

Aprendi a programar em c e então decidi que seria uma boa ideia aprender c++. Para isto estava lendo um livro no qual encontrei o seguinte pedaço de código:

double & obterHoras(int d){
    double h = 24.0 * d;
    double  &horas = h;
    return horas;
}

Ela deve receber um numero inteiro e retornar(por referencia) o equivalente em hora.

O que me causou duvida é que h é uma variável local e &horas representa uma referencia para ela. Assim que a função for executada o valor em &horas não estaria desprotegida, pois esse espaço da memoria teria sido liberado?

Esta função está errada?

Eu considero que está errada sim. Qual livro está lendo?

Introdução à programação orientada a objetos com c++, do Antonio Mendes da Silva Filho.

Pesquisei sobre esse livro e procurei nele o exemplo que você citou. Vi que no main() tem esta linha:

double horas = obterHoras(dias);

Então, apesar de obterHoras() retornar uma referência, o valor desta referência está sendo copiado para fora da função e sendo armazenado na variável horas. Por este motivo o programa funciona como planejado, não tá errado, tecnicamente falando.

Para ver melhor o que está acontecendo, experimente este código:

int main() {
  int  x = 123;
  int& r = x;
  int  y = r;

  std::cout << x << " " << y << "\n"; // 123 123

  y = 456;

  std::cout << x << " " << y; // 123 456
}

O trecho acima serve pra mostrar como os valores de x e y são independentes entre si.

O que achei estranho é que antes do trecho que você mostrou tá escrito assim:

Uma das principais motivações para fazer isso é quando a quantidade de informação a ser retornada é grande e, portanto, torna-se muito mais eficiente uma referência em vez de retornar uma cópia.

Mas o exemplo dado não ilustra nem de longe esta “eficiência” e, na verdade, pode até causar bugs chatinhos.

Acredito que o verdadeiro valor de se retornar por referência vai ficar mais claro quando você estiver trabalhando com classes. Imagine que você tem uma classe Person e quer poder acessar o valor de um atributo chamado name. Sem referência ficaria assim:

class Person {
  std::string name;

public:
  Person(std::string n): name{n} {}

  std::string getName() {
     return name;
  }
};

E para usá-la:

int main() {
  Person p{"Wellington"};

  std::cout << p.getName() << std::endl;
}

Mas isto acrescenta vários problemas. Logo no construtor, eu crio uma string e copio está string para dentro da classe. Na função getName() eu também retorno uma cópia da mesma string e estas cópias são desnecessárias uma vez que eu só quero ler o valor e não modificá-lo. Então, para melhorar eu poderia usar referências:

class Person {
  std::string name;

public:
  Person(std::string & n): name{n} {}

  std::string & getName() {
    return name;
  }
};

Isso já nos ajuda com relação as cópias desnecessárias das strings, mas acrescenta uma falha séria de encapsulamento. É esperado que a variável name seja privada e que o lado de fora da classe não possa acessá-la e muito menos alterar seu valor, porém considere o seguinte código:

int main() {
  Person p{"Wellington"};

  std::cout << p.getName() << std::endl; // Wellington
  
  p.getName() = "Juliano";

  std::cout << p.getName() << std::endl; // Juliano
    
  return 0;
}

Percebeu? Este é o tipo de perigo que linguagens como Java não nos deixa correr (claro que em Java teremos que continuar tomando cuidado com encapsulamento, mas já são outros cuidados). Para resolver é simples, basta usarmos referências constantes, asssim:

Person(const std::string & n): name{n} {}

const std::string & getName() {
  return name;
}

A palavra-chave const faz toda a diferença aqui e vai fazer com que o compilador gere um erro quando chegar em:

p.getName() = "Juliano";

Não sei em que ponto do estudo vc já está, mas se eu mostrei alguma coisa que vc ainda não aprendeu, não se preocupe, os exemplos servem apenas para dar uma prévia do que está por vir. C++ é bastante poderosa e nos dá controle máximo sobre nosso programa, estude com calma e continue não aceitando tudo o que vê em livros, apostilas, videos ou em respostas em fóruns, questione tudo. Flw.

Eu não entendi muito ao certo o código, mas creio que capturei a ideia.

Ao meu ver faz sentido usar referencia com os objetos, visto que esse objeto continua salvo na memoria(a memoria continua alocada, aquela informação a qual a referencia aponta continua lá), mas no exemplo do livro a referencia é feita a uma variável local. Isto que me causa duvida.
Quando a função acaba de ser executada, a memoria da variável local não é liberada e assim temos uma referencia para um local na memoria “desprotegido”?

Seguindo sua linha de pensamento, sim, quando a função acaba a memória é “liberada” e não há mais certeza de que o valor naquela memória será o esperado.

Contudo, o que eu quis dizer é que aqui:

double horas = obterHoras(dias);

o valor para o qual a referência aponta está sendo copiado para dentro da variável horas. Ou seja horas é uma região completamente nova com seu próprio valor e por isso tá tudo certo.

Se fosse assim:

double & refHoras = obterHoras(dias);

Aí correriámos o riscos de ter em refHoras um valor não esperado.

Compreendi, realmente dá certo. Mas é arriscado deixar do modo que estava, não é?

O meu medo é que algo aconteça nesse espaço de tempo entre a liberação(da memoria) da variável local e a atribuição do valor em “double horas = obterHoras(dias);”.

É muita neurose minha pensar que algo assim possa acontecer?

kkk… Bom, também to aprendendo, mas acredito que você pode ficar tranquilo quanto a algo acontecer neste espaço de tempo e ficar seguro de que horas conterá o valor correto. O problema é que o exemplo dado é bem ruim mesmo.

1 curtida