2010-07-19 3 views
0

J'ai donc une conversion plutôt tumultueuse en C++ de Java/C#. Même si j'ai l'impression de comprendre la plupart des bases, il y a de gros trous béants dans ma compréhension.C++ et renvoyer un null - ce qui fonctionnait en Java ne fonctionne pas en C++

Par exemple, considérons la fonction suivante:

Fruit& 
FruitBasket::getFruitByName(std::string fruitName) 
{ 
    std::map<std::string,Fruit>::iterator it = _fruitInTheBascit.find(fruitName); 
    if(it != _fruitInTheBascit.end()) 
    { 
     return (*it).second; 
    } 
    else 
    { 
      //I would so love to just return null here 
    } 

} 

_fruitsInTheBascit est un std::map<std::string,Fruit>. Si je demande getFruitByName("kumquat") vous savez qu'il ne va pas être là - qui mange des kumquats? Mais je ne veux pas que mon programme plante. Que devrait-on faire dans ces cas?

P.S. parlez-moi de toute autre stupidité que je n'ai pas déjà identifiée.

+0

Pourquoi ne pas simplement mettre 'namespace std'? Allez-vous utiliser un autre espace de noms ou quelque chose? –

+4

@thyrgle Beaucoup de gens préfèrent écrire explicitement l'espace de noms –

+6

@thyrgle: "Pourquoi ne pas simplement utiliser l'espace de noms std" Probablement parce qu'il tirera * tout * de std ::, ce qui n'est pas une bonne idée. Vraiment, en utilisant typedefs ou "en utilisant std :: map;" au lieu de "using namespace std;" est beaucoup plus élégant ... – SigTerm

Répondre

9

Il n'existe rien de tel dans C++ en tant que référence nulle, donc si la fonction renvoie une référence, vous ne pouvez pas retourner null. Vous avez plusieurs options:

  1. Modifiez le type de retour afin que la fonction renvoie un pointeur; retourne null si l'élément est introuvable.

  2. Conserver le type de retour de référence mais avoir une sorte d'objet fruit "sentinelle" et retourner une référence si l'objet n'est pas trouvé.

  3. Conserver le type de retour de référence et lever une exception (par exemple, FruitNotFoundException) si le fruit n'est pas trouvé sur la carte.

J'ai tendance à utiliser (1) si une défaillance est probable et (3) si une défaillance est improbable, où «probable» est une mesure complètement subjective. Je pense que (2) est un peu un hack, mais je l'ai vu utilisé proprement dans certaines circonstances.

Comme exemple d'un échec "improbable": dans mon projet actuel, j'ai une classe qui gère les objets et a une fonction is_object_present qui retourne si un objet est présent et une fonction get_object qui renvoie l'objet. Je m'attends toujours à ce qu'un appelant ait vérifié l'existence d'un objet en appelant le is_object_present avant d'appeler le get_object, donc un échec dans ce cas est assez improbable.

+0

Une 4ème option est de renvoyer bool indiquant le succès de la recherche, et d'utiliser un argument & Fruit pour retourner l'objet réel. Cela dit, je préfère aussi (1). – bvanvugt

+1

@ 210: Vrai, mais j'évite généralement des paramètres comme la peste; ils diminuent le nombre de places que je peux écrire 'const' dans mon code :-). Si le type est constructible par défaut, vous pouvez retourner une paire , ce que j'ai fait à l'occasion, mais je ne pense pas que ce soit aussi propre que d'autres options. –

+0

@ 210: Cela signifie également que vous devez copier le fruit dans le paramètre de sortie. Si le fruit est plutôt grand, la copie peut être très coûteuse. Mais sinon, je suis d'accord avec James ce n'est pas très élégant. –

1

Si vous avez besoin de NULL, vous pouvez renvoyer un pointeur au lieu d'une référence.

1

La raison pour laquelle cela ne fonctionne pas est que votre fonction renvoie une référence. La référence doit toujours être des instances réelles. Java n'est pas C++. Une façon de résoudre ce problème est de changer la fonction pour retourner un pointeur, qui fonctionne beaucoup plus comme les références utilisées par Java. Dans ce cas, vous pouvez simplement return null;.

Fruit* 
FruitBasket::getFruitByName(std::string fruitName) 
{ 
    std::map<std::string,Fruit>::iterator it = _fruitInTheBascit.find(fruitName); 
    if(it != _fruitInTheBascit.end()) 
    { 
     return &(*it).second; 
    } 
    else 
    { 
      return NULL; 
    } 

} 

Si vous souhaitez éviter de faire cela, pour une raison quelconque, vous pouvez définir un objet sentinelle et revenir qu'au lieu. quelque chose comme ça

Fruit NullFruit; 

Fruit& 
FruitBasket::getFruitByName(std::string fruitName) 
{ 
    std::map<std::string,Fruit>::iterator it = _fruitInTheBascit.find(fruitName); 
    if(it != _fruitInTheBascit.end()) 
    { 
     return (*it).second; 
    } 
    else 
    { 
     return NullFruit; 
    } 

} 

une option supplémentaire est de ne pas retourner du tout. Élever une exception

class NullFruitException: public std::exception {}; 

Fruit& 
FruitBasket::getFruitByName(std::string fruitName) 
{ 
    std::map<std::string,Fruit>::iterator it = _fruitInTheBascit.find(fruitName); 
    if(it != _fruitInTheBascit.end()) 
    { 
     return (*it).second; 
    } 
    else 
    { 
     throw NullFruitException; 
    } 

} 
1

Les références ne peuvent pas être nulles. Ils fonctionnent mieux avec des exceptions - au lieu de renvoyer un code d'erreur, vous pouvez lancer.

Vous pouvez également utiliser un "out" paramètre, avec un retour code d'erreur Valeur:

bool FruitBasket::getFruitByName(const std::string& fruitName, Fruit& fruit) 
{ 
    std::map<std::string,Fruit>::iterator it = _fruitInTheBascit.find(fruitName); 
    if(it != _fruitInTheBascit.end()) 
    { 
     fruit = (*it).second; 
     return true; 
    } 
    else 
    { 
     return false; 
    } 
} 

Ensuite, appelez comme ceci:

Fruit fruit; 
bool exists = basket.getFruitByName("apple", fruit); 
if(exists) 
{ 
    // use fruit 
} 
-1

je suis un peu rouillé le département C++. Mais est-il possible de retourner des fruits * au lieu des fruits &?

Si vous effectuez cette modification, vous pouvez dire "return null;" (ou retourner NULL, ou retourner 0 ... quelle que soit la syntaxe pour C++).

2

OK. Beaucoup de solutions
James McNellis a couvert tous les évidents.
Personnellement je préfère sa solution (1) mais il manque beaucoup de détails.

Une alternative (et je le lance comme une alternative) est de créer un type de référence Fruit qui sait si l'objet est valide. Vous pouvez ensuite renvoyer ceci à partir de votre méthode getFruitByName():

Fondamentalement, cela revient à renvoyer un pointeur; BUT Il n'y a pas de symétrie de propriété associée à un pointeur et il est donc difficile de dire si vous êtes censé supprimer le pointeur. En utilisant le type de référence de fruits, vous n'exposez pas le pointeur de sorte qu'il n'y a pas de confusion sur la propriété. Je suis sûr que boost a quelque chose de similaire mais je ne pouvais pas le trouver dans ma recherche de 20 secondes.

+0

Cela ressemble à un long chemin d'utiliser simplement un pointeur:/Peut-être que «boost :: ref» serait une meilleure «référence intelligente», car il essaie vraiment d'agir comme un seul. – GManNickG

+0

Oh, bon. Vous pourriez toujours vouloir lancer une exception si vous appelez 'getRef' quand les données sont nulles. Je suppose que cela dépend de combien vous voulez faire confiance à l'appelant. (Chose drôle ... Je viens de suggérer quelque chose presque exactement la même chose [en réponse à une autre question] (http://stackoverflow.com/questions/3284720/non-owning-holder-with-assignment-semantics/3284770#3284770)). –

+0

@GMan: Je savais que le boost avait quelque chose.Mais google "boost reference type" m'a donné beaucoup de lien vers la documentation de référence boost (pas tout à fait ce que je voulais). Alors utilisez boost :: ref beaucoup plus essayé et testé. –

0

La réponse de James McNellis le frappe. Je voudrais souligner, cependant, que vous devriez penser aux pointeurs de C++ comme étant des références de Java plutôt que les références de C++ étant comme des références de Java. Ce que vous feriez dans votre situation est similaire à ce que vous feriez en Java si vous essayiez de renvoyer un type primitif qui ne peut pas être nul. À ce moment-là, vous faites essentiellement ce que James a suggéré:

  1. Faites ce pointeur (pour une primitive en Java, cela voudrait dire en utilisant une classe wrapper tels que Integer ou Float, mais ici vous serait tout simplement utiliser un aiguille). Cependant, méfiez-vous du fait que vous ne pouvez pas renvoyer des pointeurs vers des variables sur la pile à moins que vous n'ayez besoin de gros problèmes, puisque la mémoire disparaîtra lorsque l'appel de fonction sera terminé.

  2. Lance une exception.

  3. Créer une valeur sentinelle laide à retourner (ce que je dirais presque toujours est une mauvaise idée).

Il y a probablement d'autres solutions, mais celles-ci sont les plus importantes, et James a fait un bon travail en les couvrant. Cependant, je ressens le besoin de souligner que si vous pensez à des objets sur la pile d'une manière similaire aux primitives en Java, il vous sera plus facile de comprendre comment les traiter. Et il est bon de se rappeler que les références de C++ et les références de Java sont deux bêtes complètement différentes.

0

Un objet qui peut être nul ou invalide est parfois causé par un objet Fallible. Un exemple d'implémentation que j'ai créé peut être trouvé ici: Fallible.h. Un autre exemple serait boost :: optionnel.