26

I know that this might be a duplicate of: Return a "NULL" object if search result not found

BUT, there's something different going on with my code because the asterisk doesn't solve my problem, which is this:

Normal Sphere::hit(Ray ray) {
   //stuff is done here
   if(something happens) {
       return NULL;
   }
   //other stuff
   return Normal(something, somethingElse);
}

But I get an error referencing the return NULL line: conversion from ‘int’ to non-scalar type ‘Normal’ requested

And another error and warning that referencing the last return line: warning: taking address of temporary and conversion from ‘Normal*’ to non-scalar type 'Normal' requested

I understand why I am getting this warning, but I don't know how to fix it. How do I return a Normal object in the last line that persists after the function ends and how do I return a NULL object that first time? (If there's a term for these types of returns, please let me know so I can also read up on it more.)

To clarify a commenter's question, I've tried these things:

I tried doing this: Normal *Sphere::hit(Ray ray) in the cpp file and Normal *hit( Ray ray ); in the header file and I get this error: error: prototype for ‘Normal* Sphere::hit(Ray)’ does not match any in class 'Sphere'

I also tried this: Normal Sphere::*hit(Ray ray) in the cpp file and Normal *hit( Ray ray); in the header file and I get this error for the second return statement: cannot convert 'Normal*' to 'Normal Sphere::*' in return

Further clarification: I'm not asking about how pointers work. (That wasn't the main question.) I'm wondering about syntax regarding pointers in C++. So, given the function I've specified above, I've gleaned that I should specify a return a pointer because C++ doesn't have null objects. Got it. BUT, the problem then becomes: what should the function prototype look like? In the cpp file, I have what Bala suggested (which is what I had originally but changed it because of the following error):

Normal* Sphere::hit(Ray ray) {
   //stuff is done here
   if(something happens) {
       return NULL;
   }
   //other stuff
   return new Normal(something, somethingElse);
}

In the header file, I have Normal *hit(Ray ray), but I still get this message: prototype for 'Normal* Sphere::hit(Ray)' does not match any in class 'Sphere' At this point, it is unclear to me why it can't find that function prototype. Here is the header file:

class Sphere
{
    public:
        Sphere();
        Vector3 center;
        float radius;
        Normal* hit(Ray ray);
};

Can anyone see why it's complaining that there doesn't exist a matching prototype for hit in the Sphere class? (I might move this to a separate question...)

Community
  • 1
  • 1
Jackie Ortiz
  • 273
  • 1
  • 3
  • 5
  • 2
    "The astrisk" makes the return statement a return of a pointer, you can have null pointers, you can't have null constant objects. – ultifinitus Sep 15 '11 at 02:33
  • 3
    This question makes it clear that you need to read up on what a pointer is and how you use it. This is a non-trivial subject: not actually *hard* but picky and easy to get wrong. – dmckee --- ex-moderator kitten Sep 15 '11 at 02:36
  • 3
    You might want to consider boost::optional http://www.boost.org/doc/libs/1_47_0/libs/optional/doc/html/index.html –  Sep 15 '11 at 02:38

8 Answers8

28

I think you need something like

Normal* Sphere::hit(Ray ray) {
   //stuff is done here
   if(something happens) {
       return NULL;
   }
   //other stuff
   return new Normal(something, somethingElse);
}

to be able to return NULL;

Bala R
  • 107,317
  • 23
  • 199
  • 210
  • What would the prototype look like? I have `Normal* hit( Ray ray );` but it's not matching properly and g++ says this: `prototype for 'Normal* Sphere::hit(Ray)' does not match any in class 'Sphere'` – Jackie Ortiz Sep 15 '11 at 02:40
  • 4
    @Bala - while this is the correct answer in regard to returning `null`, the OP obviously doesn't understand the difference between returning a copy of an object vs. a pointer to one given his question; an explanation of the difference would be beneficial. – Brian Roach Sep 15 '11 at 02:41
  • 1
    @Jackie that prototype looks fine to me and seems to compile fine http://ideone.com/4nbwB – Bala R Sep 15 '11 at 02:48
  • @Brian my bad! Now that there is so much explanation, I'll just delete my answer. – Bala R Sep 15 '11 at 02:49
  • 1
    @Bala, I like you answer the most because that's what I had originally (before even what I listed in my original draft of the question), but g++ continues to complain that there is no matching prototype. I must be missing something here... I've updated the question to detail my further complications. If you have any insight as to why g++ might be throwing that error, I'd really appreciate it. – Jackie Ortiz Sep 15 '11 at 04:35
  • 4
    This is a good recipe for memory leaks. `new` and raw pointers and no ownership semantics, oh my. – jalf Sep 15 '11 at 08:01
  • to be fair, `std::unique_ptr` didn't really exist yet when this was written. – Mooing Duck Jun 30 '23 at 19:18
27

There are several fairly standard ways of doing this. There are different tradeoffs for the methods, which I'm not going to go into here.

Method 1: Throw an exception on failure.

Normal Sphere::hit(Ray ray)
{
   //stuff is done here
   if(something happens) {
       throw InvalidIntersection;
   }
   //other stuff
   return Normal(something, somethingElse);
}

void example(Ray r)
{
   try {
     Normal n = s.hit(r);
     ... SUCCESS CASE ...
   }
   catch( InvalidIntersection& )
   {
      ... FAILURE CASE ...
   }
}

Method 2 return a pointer to a newly allocated object. (You could also use smart pointers, or auto_ptrs to make this a little neater).

Normal* Sphere::hit(Ray ray)
{
   //stuff is done here
   if(something happens) {
       return NULL
   }
   //other stuff
   return new Normal(something, somethingElse);
}

void example(Ray ray)
{
  Normal * n = s.hit(ray);
  if(!n) {
     ... FAILURE CASE ...
  } else {
    ... SUCCESS CASE ...
    delete n;
  }
}

Method 3 is to update an existing object. (You could pass a reference, but a convention I use is that any output parameter is passed by pointer).

bool Sphere::hit(Ray ray, Normal* n)
{
   //stuff is done here
   if(something happens) {
       return false
   }
   //other stuff
   if(n) *n = Normal(something, somethingElse);
   return true;
}

void example(Ray ray)
{
  Normal n;
  if( s.hit(ray, &n) ) {
     ... SUCCESS CASE ...
  } else {
     ... FAILURE CASE ...
  }
}

Method 4: Return an optional<Normal> (using boost or similar)

optional<Normal> Sphere::hit(Ray ray)
{
   //stuff is done here
   if(something happens) {
       return optional<Normal>();
   }
   //other stuff
   return optional<Normal>(Normal(something, somethingElse));
}

void example(Ray ray)
{
  optional<Normal> n = s.hit(ray);
  if( n ) {
     ... SUCCESS CASE (use *n)...
  } else {
     ... FAILURE CASE ...
  }
}
dda
  • 6,030
  • 2
  • 25
  • 34
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • There's another option `Normal(smt something, smt somethingElse){ this->something = something; this->somethingElse = somethingElse; this->has_value = true; } RegionDefinition() { this->has_value = false; } smt something; smt somethingElse; bool has_value;` ;) – memory of a dream Aug 19 '21 at 13:19
10

If you use the Boost libraries, then you can use boost::optional. That gives you something that is pretty close to a null value:

boost::optional<Normal> Sphere::hit(Ray ray) {
   //stuff is done here
   if(something happens) {
       return boost::none;
   }
   //other stuff
   return Normal(something, somethingElse);
}

boost::optional< T> is a wrapper class that contains either an instance of T or boost::none (an instance of boost::none_t).

See http://www.boost.org/doc/libs/1_47_0/libs/optional/doc/html/index.html for more details.

Johan Råde
  • 20,480
  • 21
  • 73
  • 110
5

In C++ there's no such thing as a "null object". There are null pointers though. You can implement a special object of your design that you logically treat as "null" but it's not part of the C++ language.

seand
  • 5,168
  • 1
  • 24
  • 37
1

The NULL return value would only be valid if you were returning a pointer to a Normal object, NULL represents a null pointer, not a null object.

What I would do in this case is define a 'null' or invalid state for this object. Since you are working with surface normals, you can consider a normal with length == 0 an invalid state, so then you would do this:

Normal Sphere::hit(Ray ray) {
   //stuff is done here
   if(something happens) {
       return Normal();
   }
   //other stuff
   return Normal(something, somethingElse);
}

Then your normal class would have something like this:

class Normal {
public:
    Normal() : x(0), y(0), z(0), len(0) {}
    // ... other constructors here ...

    bool isValid() const { return (len != 0) };

    // ... other methods here ...

private:
    float x, y, z, len;
};
Miguel Grinberg
  • 65,299
  • 14
  • 133
  • 152
0

You shouldn't return NULL, which is a zero constant of type int, but instead, return an empty instance of class Normal constructed by the no arg constructor, usually.

So return Normal();

Generally, it is good practice to additionally have the method isEmpty() defined.

That way you could check for a Null instance like so:

if(obj_of_class_Normal.isEmpty())
       //do something. 
S.S. Anne
  • 15,171
  • 8
  • 38
  • 76
0

BUT, there's something different going on with my code because the asterisk doesn't solve my problem, which is this:

It seems that from your question you expect that simply adding a * after the class name will solve your problems. However this sort of expectation comes from a lack of understanding what a pointer is, when to use pointers and the importance of the type system. So the remainder of this answer will hopefully clarify these points.

Firstly the C++ is a strongly typed lanuage. This means that when assigning one variable to another that 2 variables in question have to be of the same type. For example assume that in the code below A and B are basic classes with no members defined:

A a;
B b;
a = b; //compiler error here due to type mismatch

This is because a and b are different types.

Now say you created a pointer by using the *. This is also a different type of variable:

A a;
A* p_a;
a = p_a; //compiler error due to type mismatch

p_a is not the same type of variable as a.

Now the error:

conversion from ‘int’ to non-scalar type ‘Normal’ requested

is generated because the expression:

return NULL

is of type int (you'll find if you look it up it's #defined as 0) but the return type of your function is Normal.

To resolve this you have to change the function return type to be a pointer to a Normal object.

Normal* Sphere::hit(Ray ray)

and return a pointer to a Normal object:

return new Normal(something, somethingElse); 

However at this stage although compiler errors should be resolved, you now have the concern of managing the dynamically allocated memory. I can't cover that in this post so I'll leave it like it is.

Update:

This is to address the 2nd part in your question. Your class declaration in your header file should be terminated with a ;.

Community
  • 1
  • 1
sashang
  • 11,704
  • 6
  • 44
  • 58
  • thanks for the update. Unfortunately, it was my fault for not copying my code over properly. The class declaration does indeed end with a semicolon. And I do indeed still have the same problem. – Jackie Ortiz Sep 15 '11 at 04:49
0

I used to face this type of problem. If you want to, for the unqualified conditon, you can just return the normal object with the lowest/ default value you want, like example:

Normal Sphere::hit(Ray ray) {
   //stuff is done here
   if(something happens) {
       return Normal(0,0); //here I take the example by constructing the 'normal' object with all value zeros
   }
   //other stuff
   return Normal(something, somethingElse);
}

Since the return type of your function is Normal, you can just pass an object of type Normal with lowest value, like example zero or something, then you can handle further when the stack of the function terminated.

Hope this solve your problem.

Arren
  • 3
  • 2