Retorna um indicador se o processo for bem sucedido ou não

5

Eu tenho a função de membro que se parece com:

Point find_the_special_point(Image img);

No entanto, essa função pode falhar em encontrar o ponto (por exemplo, o img não contém um ponto especial ).

Qual é o caminho certo para fazer a função retornar de alguma forma que o ponto não foi encontrado?

Pensei em algumas soluções, mas não tenho certeza qual delas é a melhor ou se há uma abordagem melhor:

1. Lançamento de exceção se o ponto não for encontrado:

Eu acho que é apenas estúpido porque as exceções não são para controlar o fluxo. Além disso, não encontrar um ponto é um comportamento esperado, não uma exceção.

2. Altere a declaração para:

Bool find_the_special_point(Image img , Point& pnt);

Embora pareça correto, acho (não tenho certeza) que não é tão bom retornar a saída real via argumentos.

3. Envolvendo a saída:

struct find_the_special_point_result{
    bool is found;
    Point special_point;
};
find_the_special_point_result find_the_special_point(Image img);

Parece ser uma boa solução. No entanto, suspeito que seja um pouco exagerado.

4. Retornando ponteiro em vez disso:

std::shared_ptr<Point> find_the_special_point(Image img);

e, se não for encontrado, retorne nullptr . No entanto, acho que a alocação dinâmica não foi feita para este caso. É meio que usar a ferramenta errada para alcançar outro objetivo.

Qual é o caminho da convenção para resolver esse problema?

    
por Humam Helfawi 23.02.2018 / 14:46
fonte

4 respostas

4

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.

    
por 23.02.2018 / 23:30
fonte
3

Retornando algo se algo está lá, e nada de outra forma, é exatamente para o que std::optional foi feito.

Se o seu compilador ainda não tem esse tipo e você não pode atualizar, você pode usar a versão Boost.

    
por 23.02.2018 / 16:26
fonte
1

Eu diria que a opção # 2 é o caminho a percorrer. Como você afirmou na Opção nº 1, as exceções não são destinadas a controlar o fluxo do programa; especialmente se a falha for uma opção esperada. (o que é no seu caso)

Os booleanos são usados frequentemente para isso. ie: if(flag) #do something else #do other thing

    
por 23.02.2018 / 16:29
fonte
-7

1. Lançamento de exceção se o ponto não for encontrado:

Isso é perigoso porque as exceções são muito disfarçadas. É preciso experiência com eles para corrigi-los. Se você está escrevendo isso para os outros, então eu recomendo que você evite essa opção.

2. Altere a declaração para:

Uma opção viável, mas outros desenvolvedores irão ignorar o bool.

3. Envolvendo a saída:

O mesmo que o # 2; a parte do bool provavelmente será ignorada. Além disso, talvez você queira usar e objeto em vez de uma estrutura.

4. Retornando ponteiro em vez disso:

Uma combinação do acima. A maioria dos desenvolvedores simplesmente assumirá que nullptr nunca será retornado.

Uma quinta opção: 5. Devolve um valor de guarda.

Como o ponto está em uma imagem, suas coordenadas são sempre zero ou positivas. Retornar um valor de guarda, digamos (-1,-1) , é comum, mas pode causar erros mais tarde no código, já que o valor de proteção provavelmente será ignorado.

Não há uma boa resposta para isso. Todas as opções têm problemas. O melhor que você pode fazer é seguir a prática mais comum daqueles que usarão a função.

    
por 23.02.2018 / 15:31
fonte

Tags