Em C ++, essa questão não é trivial por causa da vida útil do objeto e construtores / destruidores. Isso não importa para os tipos de POD. Mas é relevante para outros tipos e é crucialmente importante ao escrever código modelado.
Se você retornar por valor (mesmo se envolvido em uma struct ou em uma tupla), você sempre terá que instanciar este objeto. Isso pode não ser viável para objetos que adquirem um recurso em seu construtor ou que não são construtíveis por padrão.
Se você retornar pelo parâmetro de saída, isso é ainda pior. O chamador deve instanciar um objeto vazio e você deve copiar o valor de retorno nesse objeto. Isso significa que o objeto deve ter um estado "vazio" e ser mutável. Este estado vazio pode estar em desacordo com o RAII e torna o código mais frágil.
As três soluções robustas são:
- usar exceções,
- use um tipo
optional<T>
ou - retorno por ponteiro.
Tudo isso é muito bom, mas tem trocas diferentes.
Exceções permitem escrever seu código como se o valor de retorno sempre estivesse lá. No entanto, as exceções do C ++ não são adequadas para um tipo de código de retorno. Use-os somente se a saída geralmente estiver lá e sua ausência for um erro de algum tipo. Por exemplo. std::vector::at()
usa exceções quando nenhum resultado pode ser retornado porque uma verificação de limites falhou.
Um tipo opcional permite retornar por valor, mas o valor pode não ser inicializado. Isso evita os problemas mencionados acima em relação à construção do objeto retornado. Um tipo opcional pode ser considerado como uma união marcada com segurança de tipo com um único membro.
Retornar por um tipo de ponteiro é a solução mais geral, porque isso pode manipular objetos que não podem ser copiados ou movidos. Usando ponteiros inteligentes, você pode especificar a semântica de propriedade (ponteiros únicos, compartilhados ou simples para não especificados / emprestados). Isso é feito muito comumente com algoritmos baseados em iterador, já que os iteradores são um pouco semelhantes a ponteiros. A vantagem de usar tipos que possuem uma conversão booleana é que você pode facilmente verificar o valor de retorno em um condicional:
if (auto result = some_function()) {
result->foo(); // statically known that result != nullptr
}
Ponteiros ou referências são necessários de qualquer maneira ao retornar tipos polimórficos, portanto, usar ponteiros pode não ser uma grande mudança.
A desvantagem é, claro, quando você cria um novo objeto que você deseja que o chamador possua. Você deve então retornar um unique_ptr
. Mas se você, de outra forma, retornasse por valor, retornar um optional
seria melhor, já que evita alocações indiretas e de pilha desnecessárias.