Desafio C++
Evitando referências pendentes
Enunciado
Escreva uma função const std::string& escolher_maior(const std::string& a, const std::string& b) que retorna uma referência para a string de maior comprimento. Explique por que essa implementação é perigosa e proponha uma versão segura que retorne uma cópia. No main, demonstre o uso correto da versão segura.
Requisitos
- A função
escolher_maiordeve retornarconst std::string&e comparar os comprimentos. - O
maindeve chamar a função com strings locais e imprimir o resultado. - Comente no código o problema de retornar referência para parâmetro local.
- A versão segura deve retornar
std::stringpor valor.
Código inicial
#include <iostream>
#include <string>
// TODO: implemente escolher_maior (versão perigosa)
// TODO: implemente escolher_maior_seguro (versão segura)
int main() {
std::string s1 = "casa";
std::string s2 = "jardim";
// TODO: use as funções e imprima
return 0;
}
Saída esperada
Maior (perigoso): jardim
Maior (seguro): jardim
Ver dica
A versão perigosa retorna referência para um dos parâmetros, que são locais e serão destruídos ao fim da função. A versão segura retorna uma cópia.
Mostrar solução
#include <iostream>
#include <string>
// Versão perigosa: retorna referência para um parâmetro local.
// Após o retorno, a referência fica pendente (dangling).
const std::string& escolher_maior(const std::string& a, const std::string& b) {
return (a.size() >= b.size()) ? a : b;
}
// Versão segura: retorna uma cópia da string escolhida.
std::string escolher_maior_seguro(const std::string& a, const std::string& b) {
return (a.size() >= b.size()) ? a : b;
}
int main() {
std::string s1 = "casa";
std::string s2 = "jardim";
// Uso perigoso: a referência retornada aponta para um dos parâmetros,
// que são destruídos ao final da função. Usar isso é comportamento indefinido.
// const std::string& ref = escolher_maior(s1, s2); // NÃO FAÇA ISSO
// std::cout << "Maior (perigoso): " << ref << '\n';
// Para demonstrar, chamamos imediatamente e imprimimos antes do fim da expressão.
// Ainda assim, é arriscado. O correto é usar a versão segura.
std::cout << "Maior (perigoso): " << escolher_maior(s1, s2) << '\n';
// Versão segura: retorna uma cópia, sem risco de referência pendente.
std::string maior = escolher_maior_seguro(s1, s2);
std::cout << "Maior (seguro): " << maior << '\n';
return 0;
}
Passo a passo
- A função
escolher_maiorrecebe duas referências constantes e retorna uma referência constante para a string de maior tamanho. - Dentro da função, os parâmetros
aebsão referências para as strings originais, mas eles próprios são variáveis locais da função. - Ao retornar
aoub, retornamos uma referência para um objeto que será destruído quando a função terminar (se os argumentos forem temporários) ou que pode ser válido se os argumentos forem variáveis do chamador. No entanto, o compilador não sabe disso e a referência pode ficar pendente. - No
main, evitamos armazenar a referência retornada em uma variável. Em vez disso, usamos a expressão diretamente na saída, mas isso ainda é perigoso e serve apenas para ilustração. - A função
escolher_maior_seguroretornastd::stringpor valor, criando uma cópia da string escolhida. Essa cópia é segura e pode ser usada à vontade. - No
main, armazenamos o resultado da versão segura em uma variável local e imprimimos.
Por que funciona
A versão segura funciona porque retorna um objeto por valor, que é copiado (ou movido) para o chamador. A cópia tem tempo de vida controlado pelo chamador, evitando referências pendentes. A versão perigosa tenta evitar a cópia, mas viola as regras de tempo de vida: referências não devem outliver o objeto referenciado.
Erros comuns
- Armazenar a referência retornada:
const std::string& ref = escolher_maior(s1, s2);cria uma referência pendente ses1es2forem temporários ou se a função retornar referência para local. Mesmo com variáveis locais, o compilador pode não detectar, mas é arriscado. - Retornar referência para variável local:
const std::string& f() { std::string s = "x"; return s; }retorna referência paras, que é destruída. O compilador geralmente avisa, mas é erro grave. - Achar que
const&sempre é seguro:const&apenas indica que não modificaremos o objeto, mas não garante que ele estará vivo. - Ignorar avisos do compilador: muitos compiladores avisam sobre retorno de referência para local; não ignore.
Outra forma de resolver
Usar std::string_view (C++17) para evitar cópias sem possuir o dado, mas ainda é necessário garantir que o dado subjacente permaneça vivo:
#include <string_view>
std::string_view escolher_maior_view(std::string_view a, std::string_view b) {
return (a.size() >= b.size()) ? a : b;
}
string_view é uma referência não proprietária; deve ser usada apenas enquanto as strings originais existirem.
Saída esperada
Maior (perigoso): jardim
Maior (seguro): jardim