49 votes

Vous renvoyez une "référence NULL" en C ++?

Dans typées dynamiquement des langages comme le JavaScript ou le PHP, je fais souvent des fonctions telles que:

function getSomething(name) {
    if (content_[name]) return content_[name];
    return null; // doesn't exist
}

- Je retourner un objet s'il existe ou null si pas.

Quel serait l'équivalent en C++ à l'aide de références? Est-il un modèle recommandé en général? J'ai vu que certains cadres ayant un isNull() méthode pour cela:

SomeResource SomeClass::getSomething(std::string name) {
    if (content_.find(name) != content_.end()) return content_[name];
    SomeResource output; // Create a "null" resource
    return output;
}

Ensuite, l'appelant serait de vérifier les ressources de cette façon:

SomeResource r = obj.getSomething("something");
if (!r.isNull()) {
    // OK
} else {
    // NOT OK
}

Cependant, la nécessité de mettre en œuvre ce type de méthode magique pour chaque classe me semble lourd. Aussi il ne semble pas évident lorsque l'état interne de l'objet doit être défini à partir de "nul" à "not null".

Est-il une alternative à ce modèle? Je sais déjà qu'il peut être fait en utilisant des pointeurs, mais je me demande comment/si cela peut être fait avec des références. Ou devrais-je renoncer à de revenir "null" objets en C++ et utiliser C++-modèle spécifique? Toute suggestion sur la bonne façon de le faire serait appréciée.

51voto

Sven Points 10540

Vous ne pouvez pas le faire au cours des références, comme ils le devraient jamais être NULL. Il existe essentiellement trois options, à l'aide d'un pointeur, les autres à l'aide de la valeur sémantique.

  1. Avec un pointeur (note: ceci nécessite que la ressource n'obtenez pas été détruits pendant que l'appelant dispose d'un pointeur vers elle; assurez-vous également que l'appelant sait qu'il n'a pas besoin de supprimer l'objet):

    SomeResource* SomeClass::getSomething(std::string name) {
        std::map<std::string, SomeResource>::iterator it = content_.find(name);
        if (it != content_.end()) 
            return &(*it);  
        return NULL;  
    }
    
  2. À l'aide de std::pair avec un bool pour indiquer si l'élément est valide ou pas (note: nécessite que SomeResource possède un constructeur par défaut et n'est pas cher à construire):

    std::pair<SomeResource, bool> SomeClass::getSomething(std::string name) {
        std::map<std::string, SomeResource>::iterator it = content_.find(name);
        if (it != content_.end()) 
            return std::make_pair(*it, true);  
        return std::make_pair(SomeResource(), false);  
    }
    
  3. À l'aide de boost::optional:

    boost::optional<SomeResource> SomeClass::getSomething(std::string name) {
        std::map<std::string, SomeResource>::iterator it = content_.find(name);
        if (it != content_.end()) 
            return *it;  
        return boost::optional<SomeResource>();  
    }
    

Si vous voulez de la valeur sémantique et avoir la capacité de utiliser Boost, je vous recommande l'option trois. Le principal avantage de l' boost::optional sur std::pair est un non boost::optional de la valeur n'est pas de construire le type de son encapsulation. Cela signifie qu'il fonctionne pour les types qui n'ont pas de constructeur par défaut et permet de gagner du temps/de la mémoire pour les types avec un non-trivial de constructeur par défaut.

J'ai aussi modifié votre exemple, si vous n'êtes pas à la recherche de la carte deux fois (en réutilisant l'itérateur).

29voto

jalf Points 142628

Pourquoi "en plus d'utiliser des pointeurs"? L'utilisation de pointeurs est la façon dont vous le faites en C ++. Sauf si vous définissez un type "facultatif" qui a quelque chose comme la fonction isNull() vous avez mentionnée. (ou utilisez-en un existant, comme boost::optional )

Les références sont conçues et garanties pour ne jamais être nulles . Demander "alors comment les rendre nuls" n'a pas de sens. Vous utilisez des pointeurs lorsque vous avez besoin d'une "référence nullable".

6voto

juanchopanza Points 115680

Un agréable et relativement non-intrusive approche, qui permet d'éviter le problème si la mise en œuvre de méthodes spéciales pour tous les types, c'est qu'utilisé avec boost.facultatif. Il s'agit essentiellement d'un modèle wrapper qui permet de vérifier si la valeur est "valide" ou pas.

BTW, je pense que c'est bien expliqué dans les docs, mais méfiez-vous de l' boost::optional de bool, c'est une construction qui est difficile à interpréter.

Edit: La question porte sur les "NULL référence", mais l'extrait de code a une fonction qui renvoie en valeur. Si cette fonction en effet retourné une référence:

const someResource& getSomething(const std::string& name) const ; // and possibly non-const version

alors la fonction n'a de sens que si l' someResource visé, avait une durée de vie au moins aussi longue que celle de l'objet le retour de la référence (sinon vous woul dhave une balançant de référence). Dans ce cas, il semble parfaitement bien de retourner un pointeur:

const someResource* getSomething(const std::string& name) const; // and possibly non-const version

mais vous devez le rendre absolument clair que l'appelant n'a pas la propriété de l'aiguille, et ne doit pas tenter de le supprimer.

5voto

vhallac Points 6425

Je peux penser à quelques façons de gérer cela:

  • Comme d'autres l'ont suggéré, utilisez boost::optional
  • Faites en sorte que l'objet ait un état qui indique qu'il n'est pas valide (Yuk!)
  • Utiliser un pointeur au lieu d'une référence
  • Avoir une instance spéciale de la classe qui est l'objet nul
  • Lancez une exception pour indiquer l'échec (pas toujours applicable)

4voto

Roee Gavirel Points 4550

contrairement à Java et C # dans l'objet de référence C ++ ne peut pas être nul.
donc je conseillerais 2 méthodes que j'utilise dans ce cas.

1 - au lieu de référence, utilisez un type qui a un null tel que std :: shared_ptr

2 - obtenir la référence en tant que paramètre de sortie et renvoyer un booléen pour réussir.

 bool SomeClass::getSomething(std::string name, SomeResource& outParam) {
    if (content_.find(name) != content_.end()) 
    {
        outParam = content_[name];
        return true;
    }
    return false;
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X