Как вернуть объект NULL в С++

Я знаю, что это может быть дубликат: Возврат "NULL" объект, если результат поиска не найден

НО, что-то другое происходит с моим кодом, потому что звездочка не решает мою проблему, а именно:

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

Но я получаю сообщение об ошибке в строке return NULL: conversion from ‘int’ to non-scalar type ‘Normal’ requested

И еще одна ошибка и предупреждение, ссылающиеся на последнюю обратную линию: warning: taking address of temporary и conversion from ‘Normal*’ to non-scalar type 'Normal' requested

Я понимаю, почему я получаю это предупреждение, но я не знаю, как это исправить. Как вернуть объект Normal в последней строке, которая сохраняется после завершения функции и как я могу вернуть объект NULL в первый раз? (Если у вас есть термин для этих типов возвратов, пожалуйста, дайте мне знать, чтобы я мог также прочитать об этом больше.)

Чтобы прояснить вопрос комментатора, я пробовал следующие вещи:

Я попытался сделать это: Normal *Sphere::hit(Ray ray) в файле cpp и Normal *hit( Ray ray ); в файле заголовка, и я получаю эту ошибку: error: prototype for ‘Normal* Sphere::hit(Ray)’ does not match any in class 'Sphere'

Я также пробовал это: Normal Sphere::*hit(Ray ray) в файле cpp и Normal *hit( Ray ray); в файле заголовка, и я получаю эту ошибку для второго оператора return: cannot convert 'Normal*' to 'Normal Sphere::*' in return

Дальнейшие разъяснения: Я не спрашиваю, как работают указатели. (Это был не главный вопрос.) Мне интересно, как синтаксис относительно указателей в С++. Итак, учитывая указанную выше функцию, я понял, что должен указывать возвращаемый указатель, потому что у С++ нет нулевых объектов. Понял. НО, тогда возникает проблема: как должен выглядеть прототип функции? В файле cpp у меня есть то, что предложил Бала (это то, что я изначально, но изменил его из-за следующей ошибки):

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

В файле заголовка у меня есть Normal *hit(Ray ray), но я все еще получаю это сообщение: prototype for 'Normal* Sphere::hit(Ray)' does not match any in class 'Sphere' В этот момент мне непонятно, почему он не может найти этот прототип функции. Вот заголовочный файл:

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

Может ли кто-нибудь понять, почему он жалуется, что не существует подходящего прототипа для hit в классе Sphere? (Я мог бы переместить это на отдельный вопрос...)

Ответ 1

Я думаю, вам нужно что-то вроде

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

чтобы иметь возможность возвращать NULL;

Ответ 2

Есть несколько довольно стандартных способов сделать это. Существуют различные компромиссы для методов, которые я не буду здесь обсуждать.

Способ 1. Создайте исключение при сбое.

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

Метод 2 возвращает указатель на вновь выделенный объект. (Вы также можете использовать умные указатели или auto_ptrs, чтобы сделать это немного лучше).

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

Метод 3 - обновить существующий объект. (Вы можете передать ссылку, но я использую соглашение, что любой выходной параметр передается по указателю).

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

Метод 4: вернуть optional<Normal> (используя повышение или аналогичное)

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

Ответ 3

Если вы используете библиотеки Boost, вы можете использовать boost:: optional. Это дает вам нечто, близкое к нулевому значению:

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

подталкивание:: опционально <T> - это класс оболочки, содержащий либо экземпляр T, либо boost:: none (экземпляр boost:: none_t).

Подробнее см. http://www.boost.org/doc/libs/1_47_0/libs/optional/doc/html/index.html.

Ответ 4

В С++ нет такой вещи, как "нулевой объект". Однако есть нулевые указатели. Вы можете реализовать специальный объект вашего дизайна, который вы логически считаете "нулевым", но не являетесь частью языка С++.

Ответ 5

Возвращаемое значение NULL будет действительным только в том случае, если вы возвращали указатель на объект Normal, NULL представляет нулевой указатель, а не нулевой объект.

Что бы я делал в этом случае, это определить 'null' или недопустимое состояние для этого объекта. Поскольку вы работаете с нормалями поверхности, вы можете считать нормальный с длиной == 0 недопустимым состоянием, поэтому тогда вы сделаете следующее:

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

Тогда ваш обычный класс будет иметь что-то вроде этого:

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

Ответ 6

НО, что-то другое происходит с моим кодом, потому что Звездочка не решает мою проблему, вот что:

Кажется, что из вашего вопроса вы ожидаете, что просто добавив * после того, как имя класса решит ваши проблемы. Однако такое ожидание происходит из-за недостаточного понимания того, что такое указатель, когда использовать указатели и важность системы типов. Поэтому оставшаяся часть этого ответа, мы надеемся, прояснит эти моменты.

Во-первых, С++ является строго типизированным lanuage. Это означает, что при присвоении одной переменной другой переменной 2 переменные должны быть одного типа. Например, предположим, что в приведенном ниже коде A и B являются базовыми классами без определенных членов:

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

Это связано с тем, что a и b являются разными типами.

Теперь скажите, что вы создали указатель, используя *. Это также другой тип переменной:

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

p_a не является тем же типом переменной, что и a.

Теперь ошибка:

преобразование из 'int в нескалярный тип' Нормальный запрошенный

создается потому, что выражение:

return NULL

имеет тип int (вы найдете, если вы посмотрите его #defined как 0), но возвращаемый тип вашей функции - Normal.

Чтобы решить эту проблему, вы должны изменить возвращаемый тип функции как объект pointer to a Normal.

Normal* Sphere::hit(Ray ray)

и вернуть объект pointer to a Normal:

return new Normal(something, somethingElse); 

Однако на этом этапе, хотя ошибки компилятора должны быть устранены, теперь у вас есть проблема управления динамически распределенной памятью. Я не могу покрыть это в этом посте, поэтому я оставлю его так, как есть.

Обновление:

Это касается второй части вашего вопроса. Ваше объявление класса в файле заголовка должно быть завершено с помощью ;.

Ответ 7

Вы не должны возвращать NULL, который является нулевой константой типа int, но вместо этого возвращать пустой экземпляр класса Normal, созданный конструктором no arg, обычно.

Так что вернись Normal();

Как правило, рекомендуется дополнительно определять метод isEmpty().

Таким образом, вы можете проверить наличие экземпляра Null следующим образом:

if(obj_of_class_Normal.isEmpty())
       //do something.