Retorna um objeto “NULL” se o resultado da pesquisa não for encontrado


94

Eu sou muito novo em C ++, então tendo a projetar com muitos Java-ismos enquanto estou aprendendo. De qualquer forma, em Java, se eu tivesse uma classe com um método de 'pesquisa' que retornasse um objeto Tde um Collection< T >que correspondesse a um parâmetro específico, eu retornaria esse objeto e se o objeto não fosse encontrado na coleção, eu retornaria null. Então, em minha função de chamada, eu apenas verificariaif(tResult != null) { ... }

Em C ++, estou descobrindo que não posso retornar um nullvalor se o objeto não existir. Eu só quero retornar um 'indicador' do tipo T que notifica a função de chamada que nenhum objeto foi encontrado. Não quero lançar uma exceção porque não é realmente uma circunstância excepcional.

É assim que meu código se parece agora:

class Node {
    Attr& getAttribute(const string& attribute_name) const {
       //search collection
       //if found at i
            return attributes[i];
       //if not found
            return NULL; // what should this be?
    }

private:
    vector<Attr> attributes;
}

Como posso alterá-lo para dar esse tipo de marcador?


6
Exceção e NULL nem sempre são as únicas soluções. Freqüentemente, você pode escolher um valor para retornar indicando não encontrado: por exemplo, std::find(first, last, value)retorna lastse nenhum elemento corresponder.
Cascabel

Respostas:


70

Em C ++, as referências não podem ser nulas. Se quiser retornar nulo opcionalmente se nada for encontrado, você precisará retornar um ponteiro, não uma referência:

Attr *getAttribute(const string& attribute_name) const {
   //search collection
   //if found at i
        return &attributes[i];
   //if not found
        return nullptr;
}

Caso contrário, se você insiste em retornar por referência, deve lançar uma exceção se o atributo não for encontrado.

(A propósito, estou um pouco preocupado com o seu método ser conste retornar um não- constatributo. Por razões filosóficas, sugiro retornar const Attr *. Se você também quiser modificar esse atributo, pode sobrecarregar com um não- constmétodo retornando um não- constatributo também.)


2
Obrigado. A propósito, essa é uma forma aceita de projetar tal rotina?
aduric

6
@aduric: Sim. As referências implicam que o resultado deve existir. Os ponteiros indicam que o resultado pode não existir.
Bill

7
Só por curiosidade, devemos retornar em nullptrvez de NULLpara c ++ 11 agora?
Espectral de

1
sim, sempre use nullptr em vez de NULL em C ++ 11 e posterior. se você precisa ser compatível com as versões anteriores, não o faça
Conrad Jones

56

Existem várias respostas possíveis aqui. Você deseja devolver algo que pode existir. Aqui estão algumas opções, desde a minha menos preferida até a mais preferida:

  • Retorne por referência e o sinal não pode ser encontrado por exceção.

    Attr& getAttribute(const string& attribute_name) const 
    {
       //search collection
       //if found at i
            return attributes[i];
       //if not found
            throw no_such_attribute_error;
    }

É provável que não localizar atributos seja uma parte normal da execução e, portanto, não muito excepcional. O manuseio para isso seria barulhento. Um valor nulo não pode ser retornado porque é um comportamento indefinido ter referências nulas.

  • Retorno por ponteiro

    Attr* getAttribute(const string& attribute_name) const 
    {
       //search collection
       //if found at i
            return &attributes[i];
       //if not found
            return nullptr;
    }

É fácil esquecer de verificar se um resultado de getAttribute seria um ponteiro não NULL e é uma fonte fácil de bugs.

  • Use Boost.Optional

    boost::optional<Attr&> getAttribute(const string& attribute_name) const 
    {
       //search collection
       //if found at i
            return attributes[i];
       //if not found
            return boost::optional<Attr&>();
    }

Um boost :: optional significa exatamente o que está acontecendo aqui, e possui métodos fáceis para inspecionar se tal atributo foi encontrado.


Nota lateral: std :: optional foi recentemente votado em C ++ 17, então isso será uma coisa "padrão" em um futuro próximo.


+1 Eu apenas mencionaria boost :: opcional primeiro, e apenas mencionaria brevemente as outras alternativas.
Nemanja Trifunovic

Sim, eu vi boost :: optional mencionado em algum lugar, mas eu estava pensando que exigia muito overhead. Se usá-lo for a melhor abordagem para esse tipo de problema, vou começar a usá-lo.
aduric

boost::optionalnão envolve muita sobrecarga (sem alocação dinâmica), por isso é tão bom. Usá-lo com valores polimórficos requer referências ou ponteiros de agrupamento.
Matthieu M.

2
@MatthieuM. É provável que a sobrecarga adúrica se referisse não ao desempenho, mas ao custo de incluir uma biblioteca externa no projeto.
Swoogan

Um adendo à minha resposta: observe que há um movimento em andamento para padronizar o opcional como um componente std, provavelmente para o que pode muito bem ser C ++ 17. Portanto, vale a pena conhecer esta técnica.
Kaz Dragon de

22

Você pode criar facilmente um objeto estático que representa um retorno NULL.

class Attr;
extern Attr AttrNull;

class Node { 
.... 

Attr& getAttribute(const string& attribute_name) const { 
   //search collection 
   //if found at i 
        return attributes[i]; 
   //if not found 
        return AttrNull; 
} 

bool IsNull(const Attr& test) const {
    return &test == &AttrNull;
}

 private: 
   vector<Attr> attributes; 
};

E em algum lugar em um arquivo de origem:

static Attr AttrNull;

NodeNull não deveria ser do tipo Attr?
aduric


2

Como você percebeu, não pode fazer da maneira que fazia em Java (ou C #). Aqui está outra sugestão, você poderia passar a referência do objeto como um argumento e retornar o valor bool. Se o resultado for encontrado em sua coleção, você pode atribuí-lo à referência que está sendo passada e retornar 'verdadeiro', caso contrário, retornar 'falso'. Por favor, considere este código.

typedef std::map<string, Operator> OPERATORS_MAP;

bool OperatorList::tryGetOperator(string token, Operator& op)
{
    bool val = false;

    OPERATORS_MAP::iterator it = m_operators.find(token);
    if (it != m_operators.end())
    {
        op = it->second;
        val = true;
    }
    return val;
}

A função acima deve encontrar o Operador contra a chave 'token', se encontrar o que retorna verdadeiro e atribuir o valor ao parâmetro Operador & op.

O código do chamador para esta rotina se parece com este

Operator opr;
if (OperatorList::tryGetOperator(strOperator, opr))
{
    //Do something here if true is returned.
}

1

O motivo pelo qual você não pode retornar NULL aqui é porque você declarou seu tipo de retorno como Attr&. O trailing &torna o valor de retorno uma "referência", que é basicamente um ponteiro garantido de não ser nulo para um objeto existente. Se você quiser retornar null, mude Attr&para Attr*.


0

Você não pode retornar NULLporque o tipo de retorno da função é um objeto referencee não um pointer.


-3

Você pode tentar isto:

return &Type();

6
Embora este snippet de código possa resolver a questão, incluir uma explicação realmente ajuda a melhorar a qualidade de sua postagem. Lembre-se de que você está respondendo à pergunta para leitores no futuro e essas pessoas podem não saber os motivos de sua sugestão de código.
NathanOliver

Isso provavelmente retorna uma referência morta para um objeto na pilha de métodos, não é?
mpromonet
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.