C ++ и возвращение нулевого значения - то, что работало в Java, не работает в C ++ - PullRequest
0 голосов
/ 20 июля 2010

Так что у меня довольно бурное преобразование в C ++ из Java / C #.Несмотря на то, что я чувствую, что понимаю большинство основ, в моем понимании есть большие жирные дыры.

Например, рассмотрим следующую функцию:

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 - это std::map<std::string,Fruit>.Если я задам вопрос getFruitByName("kumquat"), вы знаете, что его там не будет - кто ест кумкваты?Но я не хочу, чтобы моя программа зависала.Что нужно делать в этих случаях?

PS расскажите мне о любой другой глупости, которую я еще не выявил.

Ответы [ 8 ]

9 голосов
/ 20 июля 2010

В C ++ нет такой вещи, как нулевая ссылка, поэтому, если функция возвращает ссылку, вы не можете вернуть нулевую ссылку. У вас есть несколько вариантов:

  1. Измените тип возвращаемого значения, чтобы функция возвращала указатель; вернуть ноль, если элемент не найден.

  2. Сохраните ссылочный тип возвращаемого значения, но используйте какой-нибудь фруктовый объект "страж" и возвращайте ссылку на него, если объект не найден.

  3. Сохраните ссылочный тип возврата и сгенерируйте исключение (например, FruitNotFoundException), если фрукт не найден на карте.

Я склонен использовать (1), если сбой вероятен, и (3), если сбой маловероятен, где «вероятность» является полностью субъективной мерой. Я думаю (2), что-то вроде хака, но я видел, что он использовался аккуратно в некоторых обстоятельствах.

В качестве примера «маловероятного» сбоя: в моем текущем проекте у меня есть класс, который управляет объектами и имеет функцию is_object_present, которая возвращает наличие объекта, и функцию get_object, которая возвращает объект. Я всегда ожидаю, что вызывающая сторона проверит существование объекта путем вызова is_object_present перед вызовом get_object, поэтому сбой в этом случае весьма маловероятен.

2 голосов
/ 20 июля 2010

OK. Много решений.
Джеймс МакНеллис раскрыл все очевидные.
Лично я предпочитаю его решение (1), но многие детали отсутствуют.

Альтернатива (и я выбрасываю ее просто как альтернативу) - создать ссылочный тип Fruit, который знает, допустим ли объект. Затем вы можете вернуть это из вашего метода getFruitByName ():

По сути, это то же самое, что возвращать указатель; НО нет никакой символики владения, связанной с указателем, и, следовательно, трудно сказать, если вы должны удалить указатель. Используя ссылочный тип fruit, вы не выставляете указатель, поэтому это не приводит к путанице в отношении владельца.

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

Я уверен, что в boost есть что-то похожее, но я не смог найти его в своем 20-секундном поиске.

1 голос
/ 20 июля 2010

Ссылки не могут быть нулевыми.Они лучше всего работают с исключениями - вместо возврата кода ошибки вы можете выбросить.

В качестве альтернативы, вы можете использовать параметр "out" со значением кода ошибки:

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

Тогда назовите это так:

Fruit fruit;
bool exists = basket.getFruitByName("apple", fruit);
if(exists)
{
    // use fruit
}
1 голос
/ 20 июля 2010

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

Один из способов исправить это - изменить функцию так, чтобы она возвращала указатель, который работает гораздо больше, чем ссылки, используемые Java. В этом случае вы можете просто 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;
    }

}

Если вы хотите по какой-то причине избежать этого, вы можете определить объект-страж и вернуть его взамен. как то так

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

}

дополнительная опция - вообще не возвращаться. Возбудить исключение

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 голос
/ 20 июля 2010

Если вам нужно NULL, вы можете вернуть указатель вместо ссылки.

0 голосов
/ 20 июля 2010

Объект, который может быть нулевым или недействительным, иногда вызывается ошибочным объектом. Один пример реализации, который я создал, можно найти здесь: Fallible.h . Другим примером будет повышение :: необязательно.

0 голосов
/ 20 июля 2010

Ответ Джеймса МакНеллиса попал в точку. Однако я хотел бы отметить, что вы должны думать о том, что указатели C ++ похожи на ссылки Java, а не на ссылки C ++ как ссылки Java. То, что вы делаете в вашей ситуации, похоже на то, что вы делаете в Java, если пытаетесь вернуть примитивный тип, который не может быть нулевым. В этот момент вы в основном делаете то, что предложил Джеймс:

  1. Сделать это указателем (для примитива в Java это означало бы использование класса-оболочки, такого как Integer или Float, но здесь вы бы просто использовали указатель). Однако следует помнить о том, что вы не можете возвращать указатели на переменные в стеке, если не хотите больших проблем, поскольку память завершится после завершения вызова функции.

  2. Брось исключение.

  3. Создайте некрасивое возвращаемое значение для дозорного (что я почти всегда утверждаю, что это плохая идея).

Вероятно, есть и другие решения, но они являются ключевыми, и Джеймс хорошо поработал над ними. Однако я чувствую необходимость указать, что если вы будете думать об объектах в стеке аналогично примитивам в Java, вам будет проще понять, как с ними обращаться. И хорошо помнить, что ссылки на C ++ и ссылки на Java - это два совершенно разных зверя.

0 голосов
/ 20 июля 2010

Я немного заржавел в отделе C ++.Но возможно ли вернуть Fruit * вместо Fruit &?

Если вы сделаете это изменение, вы можете сказать «return null;(или вернуть NULL, или вернуть 0 ... независимо от синтаксиса C ++).

...