0

So I'm having a rather tumultuous conversion to C++ from Java/C#. Even though I feel like I understand most of the basics, there are some big fat gaping holes in my understanding.

For instance, consider the following function:

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
    }

}

Where _fruitsInTheBascit is a std::map<std::string,Fruit>. If I query getFruitByName("kumquat") you know it's not going to be there - who eats kumquats? But I don't want my program to crash. What should be done in these cases?

P.S. tell me of any other stupidity that I haven't already identified.

JnBrymn
  • 24,245
  • 28
  • 105
  • 147
  • Why don't you just put `using namespace std`? Are you going to be using another namespace or something? –  Jul 19 '10 at 23:48
  • 4
    @thyrgle A lot of people prefer to write the namespace explicitly – Michael Mrozek Jul 19 '10 at 23:52
  • 6
    @thyrgle: "Why don't you just put using namespace std" Probably because it will pull *everything* from std::, which isn't a good idea. Really, using typedefs or "using std::map;" instead of "using namespace std;" is much more elegant... – SigTerm Jul 19 '10 at 23:54
  • 4
    Why should you expect what worked in other languages would work in C++? In fact, what makes you think you can simply pick-up C++ just because you knew some other languages? That's not the way to learn, and leads to failure. You need to get a [gook beginner book](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) and start from the ground up. If something you read happens to correlate with something you already know, great; but you can't learn simply from correlation. – GManNickG Jul 19 '10 at 23:57
  • While I do think that GMan is being a bit harsh, it really does look like you should thoroughly read an intro C++ book. Even if you're able to read it quickly, it will definitely show you a number of holes in your understanding. C++ is very similar to Java in many respects, but it's also very different, and it's easy to shoot yourself in the foot because you *think* that you understand how something works. – Jonathan M Davis Jul 20 '10 at 00:22
  • 3
    A couple of other points (since you asked): I'd recommend passing in `fruitname` as a `const std::string&` so that you're not making an unnecessary copy of the string with each call. Also, I'd prefer `it->second` to `(*it).second`. – Fred Larson Jul 20 '10 at 00:52
  • 1
    @Jonathan, I agree with @GMan here. Having seen copious amounts of C++ code written by originally Java devs, it is entirely necessary, neigh a requirement, to understand basic C++ before coding. Most notably: memory management. Last C++ app I saw written by a Java dev had every `new` absolutely unmatched by a `delete`, or controlling object. Leaked memory like a sieve. And, this in an API intended for use in a long-running services. – Nathan Ernst Jul 20 '10 at 00:53
  • @Nathan: And I bet 99% of those news weren't even needed. – GManNickG Jul 21 '10 at 04:25
  • 1
    @GMan, absolutely correct. I'm actually not sure if any of them were needed at all. vectors of strings, all allocated dynamically (yes, it was vectors of pointers to strings). Horrible code. – Nathan Ernst Jul 21 '10 at 14:44
  • @Nathan Ha ha! That's funny. I'm sure I'm not doing the memory management thing right yet, but fortunately I took a long hard look at C++ some years ago before picking up Java. I never got to the point where I was any good with C++ back then, but I was at least made aware of the memory issues. It's funny to think about programming C++ as if it was Java. "but the syntax is about the same" ha ha ha – JnBrymn Jul 22 '10 at 14:06

8 Answers8

10

There is no such thing in C++ as a null reference, so if the function returns a reference, you can't return null. You have several options:

  1. Change the return type so that the function returns a pointer; return null if the element is not found.

  2. Keep the reference return type but have some sort of "sentinel" fruit object and a return a reference to it if the object is not found.

  3. Keep the reference return type and throw an exception (e.g., FruitNotFoundException) if the fruit is not found in the map.

I tend to use (1) if a failure is likely and (3) if a failure is unlikely, where "likely" is a completely subjective measure. I think (2) is a bit of a hack, but I've seen it used neatly in some circumstances.

As an example of an "unlikely" failure: in my current project, I have a class that manages objects and has a function is_object_present that returns whether an object is present and a function get_object that returns the object. I always expect that a caller will have verified the existence of an object by calling is_object_present before calling get_object, so a failure in this case is quite unlikely.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • A 4th option is to return bool indicating the success of the find, and use a Fruit& argument to return the actual object. That said, I prefer (1) as well. – bvanvugt Jul 19 '10 at 23:58
  • 1
    @210: True, but I generally avoid out parameters like the plague; they lower the number of places I can write `const` in my code :-). If the type is default constructible, you could return a `pair`, which I've done on occasion, but I don't think that's as clean as other options. – James McNellis Jul 20 '10 at 00:01
  • 1
    @210: This also means that you need to copy the fruit into the output parameter. If Fruit is rather large then copying may be very expensive. But otherwise I agree with James its just not very elegant. – Martin York Jul 20 '10 at 00:08
  • You can return a null reference: `return *static_cast(NULL);`. Not saying it's right or will work as expected (and it defeats the purpose), but you can surely return a null reference. – Nathan Ernst Jul 20 '10 at 00:56
  • 3
    @Nathan: In any well-defined program you cannot have a null reference, because the only way to obtain one is to dereference a null pointer (like you show in your comment). Dereferencing a null pointer results in undefined behavior. – James McNellis Jul 20 '10 at 01:10
2

OK. Lots of solutions.
James McNellis has covered all the obvious ones.
Personally I prefer his solution (1) but there are a lot of details missing.

An alternative (and I throw it out just as an alternative) is to create a Fruit reference type that knows if the object is valid. Then you can return this from your getFruitByName() method:

Basically it is the same as returning a pointer; BUT there is no ownership symantics associated with a pointer and thus it is hard to tell if you are supposed to delete the pointer. By using the fruit reference type you are not exposing the pointer so it leads to no confusion about the ownership.

class FruitReference
{
    public:
        FruitReference()  // When nothing was found use this.
            :data(NULL)
        {}
        FruitReference(Fruit& fruit)  // When you fidn data.
            :data(&fruit)
        {}
        bool   isValid() const { return data != NULL;}
        Fruit& getRef()  const { return *data; }
    private:
        Fruit*   data; //(not owned)
};

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

I am sure boost has somthing similar but I could not find it in my 20 second search.

Martin York
  • 257,169
  • 86
  • 333
  • 562
  • It feels like a long way of just using a pointer :/ Maybe `boost::ref` would be a better "smart reference", because it very much tries to act like one. – GManNickG Jul 20 '10 at 00:19
  • Oh, good one. You might still want to throw an exception if you call `getRef` when the data is null. I guess it depends how much you want to trust the caller. (Funny thing... I just suggested something almost exactly the same [in response to another question](http://stackoverflow.com/questions/3284720/non-owning-holder-with-assignment-semantics/3284770#3284770)). – James McNellis Jul 20 '10 at 00:21
  • @GMan: I knew boost had somthing. But google "boost reference type" gave me lots of link to the boost reference documentation (not quite what I wanted). So use boost::ref much more tried and tested. – Martin York Jul 20 '10 at 00:24
  • @Martin: Ah, yeah. +1 for this concept though. @James: Heh, funny. I was going to suggest `boost::ref` in that question as well. – GManNickG Jul 20 '10 at 00:28
  • 1
    @GMan: But `boost::reference_wrapper` isn't nullable, so what would it give us here? – Georg Fritzsche Jul 20 '10 at 00:30
  • 2
    @Georg: Oh, derp, you're right! I've been thinking of `boost::ref` all day when I really meant [`boost::optional`](http://www.boost.org/doc/libs/1_42_0/libs/optional/doc/html/index.html). Yeesh. – GManNickG Jul 20 '10 at 00:33
1

If you need NULL, you can return a pointer instead of a reference.

dan04
  • 87,747
  • 23
  • 163
  • 198
1

The reason this doesn't work is because your function returns a reference. Reference must always be actual instances. Java is not C++.

One way you could fix this is to change the function to return a pointer, which work much more like the references java uses. In that case, you can just 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;
    }

}

If you'd like to avoid doing that, for some reason, you could define a sentinel object and return that instead. something like this

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

}

an additional option is to not return at all. Raise an 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;
    }

}
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
1

References cannot be null. They work best with exceptions - instead of returning an error code, you can throw.

Alternatively, you can use an "out" parameter, with an error-code return value:

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

Then call it like this:

Fruit fruit;
bool exists = basket.getFruitByName("apple", fruit);
if(exists)
{
    // use fruit
}
jA_cOp
  • 3,275
  • 19
  • 15
0

James McNellis' answer hits it spot-on. I would point out, however, that you should be thinking of C++'s pointers as being like Java's references rather than C++'s references being like Java's references. What you'd do in your situation is similar to what you'd do in Java if you were trying to return a primitive type which can't be null. At that point, you're basically doing what James suggested:

  1. Make it pointer (for a primitive in Java, that would mean using a wrapper class such as Integer or Float, but here you would just use a pointer). However, beware of the fact that you can't return pointers to variables on the stack unless you want big trouble, since the memory will go away when the function call has completed.

  2. Throw an exception.

  3. Create an ugly sentinel value to return (which I would almost always argue is a bad idea).

There are likely other solutions, but those are the key ones, and James did a good job covering them. However, I do feel the need to point out that if you think of objects on the stack in a manner similar to primitives in Java, then it will be easier for you to figure out how to deal with them. And it's good to remember that C++'s references and Java's references are two entirely different beasts.

Jonathan M Davis
  • 37,181
  • 17
  • 72
  • 102
0

An object that can be null or invalid is sometimes caused a Fallible object. One example implementation I created can be found here: Fallible.h. Another example would be boost::optional.

StackedCrooked
  • 34,653
  • 44
  • 154
  • 278
-1

i'm a bit rusty in the C++ department. But is it possible to return Fruit* instead of Fruit& ?

If you make this change then you can say "return null;" (or return NULL, or return 0... whatever the syntax is for C++).

AlvinfromDiaspar
  • 6,611
  • 13
  • 75
  • 140