48

在 JavaScript 或 PHP 等动态类型语言中,我经常执行以下功能:

function getSomething(name) {
    if (content_[name]) return content_[name];
    return null; // doesn't exist
}

null如果它存在或不存在,我会返回一个对象。

使用引用的 C++ 中的等价物是什么?一般有什么推荐的模式吗?我看到一些框架isNull()为此提供了一种方法:

SomeResource SomeClass::getSomething(std::string name) {
    if (content_.find(name) != content_.end()) return content_[name];
    SomeResource output; // Create a "null" resource
    return output;
}

然后调用者将以这种方式检查资源:

SomeResource r = obj.getSomething("something");
if (!r.isNull()) {
    // OK
} else {
    // NOT OK
}

然而,必须为每个类实现这种神奇的方法似乎很繁重。此外,何时应将对象的内部状态从“null”设置为“not null”似乎并不明显。

这种模式有什么替代方案吗?我已经知道可以使用指针来完成,但我想知道如何/是否可以使用引用来完成。还是我应该放弃在 C++ 中返回“null”对象并使用一些 C++ 特定的模式?任何有关正确方法的建议将不胜感激。

4

8 回答 8

50

您不能在引用期间执行此操作,因为它们永远不应为 NULL。基本上有三种选择,一种使用指针,另一种使用值语义。

  1. 使用指针(注意:这要求资源在调用者有指向它的指针时不会被破坏;还要确保调用者知道它不需要删除对象):

    SomeResource* SomeClass::getSomething(std::string name) {
        std::map<std::string, SomeResource>::iterator it = content_.find(name);
        if (it != content_.end()) 
            return &(*it);  
        return NULL;  
    }
    
  2. std::pair与 a一起使用bool来指示项目是否有效(注意:要求 SomeResource 具有适当的默认构造函数并且构造成本不高):

    std::pair<SomeResource, bool> SomeClass::getSomething(std::string name) {
        std::map<std::string, SomeResource>::iterator it = content_.find(name);
        if (it != content_.end()) 
            return std::make_pair(*it, true);  
        return std::make_pair(SomeResource(), false);  
    }
    
  3. 使用boost::optional

    boost::optional<SomeResource> SomeClass::getSomething(std::string name) {
        std::map<std::string, SomeResource>::iterator it = content_.find(name);
        if (it != content_.end()) 
            return *it;  
        return boost::optional<SomeResource>();  
    }
    

如果你想要值语义并且有能力使用 Boost,我推荐选项三。boost::optionalover的主要优点std::pair是未初始化的boost::optional值不构造其封装的类型。这意味着它适用于没有默认构造函数的类型,并为具有非平凡默认构造函数的类型节省时间/内存。

我还修改了您的示例,因此您不会两次搜索地图(通过重用迭代器)。

于 2012-04-29T09:34:29.333 回答
27

为什么“除了使用指针”?使用指针您在 C++ 中执行此操作的方式。除非您定义一些“可选”类型,它具有isNull()您提到的功能。(或使用现有的,例如boost::optional

引用被设计并保证永远不会为 null。问“那么我如何使它们为空”是荒谬的。当您需要“可空引用”时,您可以使用指针。

于 2012-04-29T09:39:29.730 回答
6

一种不错且相对非侵入性的方法,如果为所有类型实现特殊方法,可以避免该问题,是与boost.optional一起使用。它本质上是一个模板包装器,允许您检查所保存的值是否“有效”。

顺便说一句,我认为这在文档中得到了很好的解释,但请注意boost::optionalbool这是一个难以解释的结构。

编辑:问题询问“NULL 引用”,但代码片段有一个按值返回的函数。如果该函数确实返回了引用:

const someResource& getSomething(const std::string& name) const ; // and possibly non-const version

那么该函数只有在someResource被引用的生命周期至少与返回引用的对象的生命周期一样长时才有意义(否则您将有一个悬空引用)。在这种情况下,返回一个指针似乎非常好:

const someResource* getSomething(const std::string& name) const; // and possibly non-const version

但是您必须绝对清楚,调用者不拥有指针的所有权,也不应该尝试删除它。

于 2012-04-29T09:34:36.563 回答
5

我可以想到几种方法来处理这个问题:

  • 正如其他人建议的那样,使用boost::optional
  • 使对象具有表明它无效的状态(Yuk!)
  • 使用指针代替引用
  • 有一个作为空对象的类的特殊实例
  • 抛出异常以指示失败(并不总是适用)
于 2012-04-29T09:43:04.333 回答
4

与 Java 和 C# 不同,C++ 中的引用对象不能为空。
所以我会建议我在这种情况下使用的 2 种方法。

1 - 使用具有 null 的类型而不是引用,例如 std::shared_ptr

2 - 获取参考作为输出参数并返回布尔值表示成功。

bool SomeClass::getSomething(std::string name, SomeResource& outParam) {
    if (content_.find(name) != content_.end()) 
    {
        outParam = content_[name];
        return true;
    }
    return false;
}
于 2012-04-29T09:40:05.687 回答
2

下面的代码演示了如何返回“无效”引用;它只是使用指针的另一种方式(传统方法)。

不建议您在将被其他人使用的代码中使用它,因为期望返回引用的函数总是返回有效引用。

#include <iostream>
#include <cstddef>

#define Nothing(Type) *(Type*)nullptr
//#define Nothing(Type) *(Type*)0

struct A { int i; };
struct B
{
    A a[5];
    B() { for (int i=0;i<5;i++) a[i].i=i+1; }
    A& GetA(int n)
    {
        if ((n>=0)&&(n<5)) return a[n];
        else return Nothing(A);
    }
};

int main()
{
    B b;
    for (int i=3;i<7;i++)
    {
        A &ra=b.GetA(i);
        if (!&ra) std::cout << i << ": ra=nothing\n";
        else std::cout << i << ": ra=" << ra.i << "\n";
    }
    return 0;
}

Nothing(Type)返回一个value,在这种情况下由nullptr- 表示,您也可以使用0,将引用的地址设置为该值。现在可以检查此地址,就好像您一直在使用指针一样。

于 2015-04-04T08:37:27.350 回答
1

这里有几个想法:

备选方案 1:

class Nullable
{
private:
    bool m_bIsNull;

protected:
    Nullable(bool bIsNull) : m_bIsNull(bIsNull) {}
    void setNull(bool bIsNull) { m_bIsNull = bIsNull; }

public:
    bool isNull();
};

class SomeResource : public Nullable
{
public:
    SomeResource() : Nullable(true) {}
    SomeResource(...) : Nullable(false) { ... }

    ...
};

备选方案 2:

template<class T>
struct Nullable<T>
{
    Nullable(const T& value_) : value(value_), isNull(false) {}
    Nullable() : isNull(true) {}

    T value;
    bool isNull;
};
于 2012-04-29T09:46:48.763 回答
0

另一种选择 - 我不时使用的一个选项,当你真的不希望返回一个“空”对象而是一个“空/无效”对象时:

// List of things
std::vector<some_struct> list_of_things;
// An emtpy / invalid instance of some_struct
some_struct empty_struct{"invalid"};

const some_struct &get_thing(int index)
{
    // If the index is valid then return the ref to the item index'ed
    if (index <= list_of_things.size())
    {
        return list_of_things[index];
    }

    // Index is out of range, return a reference to the invalid/empty instance
    return empty_struct; // doesn't exist
}

它非常简单并且(取决于你在另一端用它做什么)可以避免在另一端进行空指针检查。例如,如果您正在生成一些事物列表,例如:

for (const auto &sub_item : get_thing(2).sub_list())
{
    // If the returned item from get_thing is the empty one then the sub list will
    // be empty - no need to bother with nullptr checks etc... (in this case)
}
于 2019-09-04T12:31:32.137 回答