C++并返回 null - 在 Java 中有效的方法在 C++ 中不起作用;

发布于 2024-09-10 16:43:52 字数 649 浏览 3 评论 0原文

因此,我从 Java/C# 到 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
    }

}

其中 _fruitsInTheBascitstd::map。如果我查询 getFruitByName("kumquat") 你知道它不会在那里 - 谁吃金橘?但我不希望我的程序崩溃。遇到这些情况应该怎么办?

PS告诉我任何其他我还没有发现的愚蠢行为。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

九八野马 2024-09-17 16:43:52

C++ 中不存在空引用这样的东西,因此如果函数返回引用,则不能返回空。您有多种选择:

  1. 更改返回类型,以便函数返回指针;如果未找到该元素,则返回 null。

  2. 保留引用返回类型,但有某种“哨兵”水果对象,如果找不到该对象,则返回对其的引用。

  3. 如果在地图中找不到水果,则保留引用返回类型并引发异常(例如,FruitNotFoundException)。

我倾向于使用(1)如果可能发生故障,以及(3)如果不太可能发生故障,其中“可能”是一个完全主观的衡量标准。我认为(2)有点黑客,但我已经看到它在某些情况下被巧妙地使用。

举一个“不太可能”失败的例子:在我当前的项目中,我有一个管理对象的类,并有一个函数 is_object_present(返回对象是否存在)和一个函数 get_object code> 返回对象。我总是希望调用者在调用 get_object 之前通过调用 is_object_present 来验证对象的存在,因此在这种情况下失败的可能性很小。

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.

风苍溪 2024-09-17 16:43:52

好的。很多解决方案。
詹姆斯·麦克内利斯 (James McNellis) 涵盖了所有显而易见的问题。
我个人更喜欢他的解决方案(1),但缺少很多细节。

另一种选择(我只是将其作为替代方案抛弃)是创建一个 Fruit 引用类型,该类型知道该对象是否有效。然后你可以从 getFruitByName() 方法返回这个:

基本上它与返回一个指针相同; 但是没有与指针关联的所有权语义,因此很难判断是否应该删除该指针。通过使用水果引用类型,您不会公开指针,因此不会导致所有权混乱。

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 秒的搜索中找不到它。

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.

肤浅与狂妄 2024-09-17 16:43:52

如果需要 NULL,可以返回指针而不是引用。

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

梦中楼上月下 2024-09-17 16:43:52

引用不能为空。它们在处理异常时效果最好 - 您可以抛出异常,而不是返回错误代码。

或者,您可以使用带有错误代码返回值的“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
}

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
}
戏舞 2024-09-17 16:43:52

这不起作用的原因是你的函数返回一个引用。引用必须始终是实际实例。 Java 不是 C++。

解决此问题的一种方法是更改​​函数以返回指针,其工作方式更像 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;
    }

}

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

}
顾挽 2024-09-17 16:43:52

詹姆斯·麦克内利斯的回答一针见血。不过,我要指出的是,您应该将 C++ 的指针视为 Java 的引用,而不是将 C++ 的引用视为 Java 的引用。在您的情况下,如果您尝试返回不能为 null 的基本类型,您将执行的操作与在 Java 中执行的操作类似。此时,您基本上正在执行 James 建议的操作:

  1. 使其成为指针(对于 Java 中的原语,这意味着使用 Integer 或 Float 等包装类,但在这里您只需使用指针)。但是,请注意,除非您想遇到大麻烦,否则不能返回指向堆栈上变量的指针,因为函数调用完成后内存将消失。

  2. 引发异常。

  3. 创建一个丑陋的哨兵值来返回(我几乎总是认为这是一个坏主意)。

可能还有其他解决方案,但这些是关键的解决方案,詹姆斯很好地涵盖了这些解决方案。然而,我确实觉得有必要指出,如果你以类似于 Java 中的原语的方式来思考堆栈上的对象,那么你会更容易弄清楚如何处理它们。最好记住,C++ 的引用和 Java 的引用是两种完全不同的野兽。

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.

落花随流水 2024-09-17 16:43:52

一个可能为 null 或无效的对象有时会导致 Fallible 对象。我创建的一个示例实现可以在这里找到:易错.h。另一个例子是 boost::可选。

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.

蓝色星空 2024-09-17 16:43:52

我对 C++ 部门有点生疏。但是是否可以返回 Fruit* 而不是 Fruit& ?

如果您进行此更改,那么您可以说“return null;” (或者返回 NULL,或者返回 0...无论 C++ 的语法是什么)。

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++).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文