Renvoie un objet «NULL» si le résultat de la recherche est introuvable


94

Je suis assez nouveau en C ++, donc j'ai tendance à concevoir avec beaucoup de Java-isms pendant que j'apprends. Quoi qu'il en soit, en Java, si j'avais une classe avec une méthode de recherche qui renverrait un objet à Tpartir d'un Collection< T >qui correspond à un paramètre spécifique, je retournerais cet objet et si l'objet n'était pas trouvé dans la collection, je le retournerais null. Ensuite, dans ma fonction d'appel, je vérifierais simplementif(tResult != null) { ... }

En C ++, je découvre que je ne peux pas renvoyer une nullvaleur si l'objet n'existe pas. Je veux juste renvoyer un «indicateur» de type T qui notifie à la fonction appelante qu'aucun objet n'a été trouvé. Je ne veux pas lancer d'exception car ce n'est pas vraiment une circonstance exceptionnelle.

Voici à quoi ressemble mon code en ce moment:

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;
}

Comment puis-je le changer pour pouvoir donner ce type de marqueur?


6
Exception et NULL ne sont pas toujours les seules solutions. Vous pouvez souvent choisir une valeur à renvoyer pour indiquer que vous ne trouvez pas: par exemple, std::find(first, last, value)renvoie lastsi aucun élément ne correspond.
Cascabel

Réponses:


70

En C ++, les références ne peuvent pas être nulles. Si vous souhaitez éventuellement retourner null si rien n'est trouvé, vous devez renvoyer un pointeur, pas une référence:

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

Sinon, si vous insistez pour renvoyer par référence, vous devez lancer une exception si l'attribut n'est pas trouvé.

(Au fait, je suis un peu inquiet du fait que votre méthode soit constet renvoie un non- constattribut. Pour des raisons philosophiques, je vous suggère de revenir const Attr *. Si vous souhaitez également modifier cet attribut, vous pouvez surcharger avec une non- constméthode renvoyant également un non- constattribut.)


2
Merci. Au fait, est-ce une manière acceptée de concevoir une telle routine?
aduric

6
@aduric: Oui. Les références impliquent que le résultat doit exister. Les pointeurs impliquent que le résultat n'existe peut-être pas.
Projet de loi du

7
Juste curieux, allons-nous revenir nullptrau lieu de NULLpour c ++ 11 maintenant?
Spectral

1
yes utilisez toujours nullptr sur NULL dans C ++ 11 et versions ultérieures. si vous devez être rétrocompatible avec les versions earliver, alors ne le faites pas
Conrad Jones

56

Il y a plusieurs réponses possibles ici. Vous souhaitez renvoyer quelque chose qui pourrait exister. Voici quelques options, allant de la moins préférée à la plus préférée:

  • Retour par référence et signal introuvable par exception.

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

Il est probable que ne pas trouver d'attributs soit une partie normale de l'exécution, et donc pas très exceptionnelle. La manipulation pour cela serait bruyante. Une valeur nulle ne peut pas être retournée car il est un comportement non défini d'avoir des références nulles.

  • Retour par pointeur

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

Il est facile d'oublier de vérifier si un résultat de getAttribute serait un pointeur non NULL et constitue une source facile de bogues.

  • Utilisez Boost.

    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&>();
    }

Un boost :: optional signifie exactement ce qui se passe ici, et a des méthodes simples pour inspecter si un tel attribut a été trouvé.


Note latérale: std :: optional a été récemment voté en C ++ 17, donc ce sera une chose "standard" dans un proche avenir.


+1 Je voudrais simplement mentionner boost :: optional en premier, et ne mentionner que brièvement les autres alternatives.
Nemanja Trifunovic

Ya j'ai vu boost :: optionnel mentionné quelque part mais je pensais que cela nécessitait trop de frais généraux. Si l'utiliser est la meilleure approche pour ce genre de problèmes, je commencerai à l'utiliser.
aduric le

boost::optionaln'implique pas beaucoup de frais généraux (pas d'allocation dynamique), c'est pourquoi c'est si génial. Son utilisation avec des valeurs polymorphes nécessite des références d'encapsulation ou des pointeurs.
Matthieu M.

2
@MatthieuM. Il est probable que le surcoût auquel aduric faisait référence n'était pas la performance, mais le coût d'inclusion d'une bibliothèque externe dans le projet.
Swoogan

Un addendum à ma réponse: notez qu'il y a un mouvement en cours pour standardiser le optionnel en tant que composant std, probablement pour ce qui pourrait bien être C ++ 17. Cela vaut donc la peine de connaître cette technique.
Kaz Dragon

22

Vous pouvez facilement créer un objet statique qui représente un retour 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; 
};

Et quelque part dans un fichier source:

static Attr AttrNull;

NodeNull ne devrait-il pas être de type Attr?
aduric


2

Comme vous l'avez compris, vous ne pouvez pas le faire comme vous l'avez fait en Java (ou C #). Voici une autre suggestion, vous pouvez passer la référence de l'objet comme argument et renvoyer une valeur booléenne. Si le résultat se trouve dans votre collection, vous pouvez l'affecter à la référence passée et renvoyer «true», sinon renvoyer «false». Veuillez considérer ce code.

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;
}

La fonction ci-dessus doit trouver l'opérateur par rapport à la clé 'token', si elle trouve celle qu'elle renvoie true et affecter la valeur au paramètre Operator & op.

Le code de l'appelant pour cette routine ressemble à ceci

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

1

La raison pour laquelle vous ne pouvez pas renvoyer NULL ici est que vous avez déclaré votre type de retour comme Attr&. La fin &fait de la valeur de retour une "référence", qui est fondamentalement un pointeur garanti non nul vers un objet existant. Si vous souhaitez pouvoir renvoyer null, passez Attr&à Attr*.


0

Vous ne pouvez pas retourner NULLcar le type de retour de la fonction est un objet referenceet non un pointer.


-3

Vous pouvez essayer ceci:

return &Type();

6
Bien que cet extrait de code puisse résoudre la question, inclure une explication aide vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondez à la question aux lecteurs à l'avenir, et que ces personnes pourraient ne pas connaître les raisons de votre suggestion de code.
NathanOliver

Cela renvoie probablement une référence morte à un objet sur la pile de méthodes, n'est-ce pas?
mpromonet
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.