24

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
  • 245
  • 1
  • 2
  • 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
  • 2
    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
  • 2
    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

7 Answers7

23

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
  • 104,615
  • 23
  • 192
  • 207
  • 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
  • @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
  • 3
    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
23

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
  • 5,760
  • 2
  • 24
  • 34
Michael Anderson
  • 66,195
  • 7
  • 128
  • 177
  • 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
9

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
  • 19,452
  • 21
  • 67
  • 105
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,132
  • 1
  • 23
  • 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
  • 61,236
  • 14
  • 124
  • 145
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
  • 14,415
  • 7
  • 35
  • 68
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,006
  • 4
  • 41
  • 56
  • 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