在C ++中返回“NULL引用”?

时间:2012-04-29 09:31:19

标签: c++ object methods null design-patterns

在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 ++的模式?任何有关正确方法的建议都将受到赞赏。

8 个答案:

答案 0 :(得分:41)

在引用期间不能这样做,因为它们永远不应该为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::pairbool一起使用以指示该项是否有效(注意:要求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>();  
    }
    
  4. 如果你想要价值语义并且能够使用Boost,我建议选择三。 boost::optional优于std::pair的主要优点是,单元化boost::optional值不构造其封装类型。这意味着它适用于没有默认构造函数的类型,并为具有非平凡构造函数的类型节省时间/内存。

    我还修改了你的例子,所以你没有两次搜索地图(通过重用迭代器)。

答案 1 :(得分:24)

为什么“除了使用指针”?使用指针 就像在C ++中一样。除非你定义一些“可选”类型,它类似于你提到的isNull()函数。 (或使用现有的,如boost::optional

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

答案 2 :(得分:6)

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

BTW我认为这在文档中得到了很好的解释,但要注意boost::optional的{​​{1}},这是一个难以解释的结构。

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

bool

然后该函数只有在被引用的const someResource& getSomething(const std::string& name) const ; // and possibly non-const version 的生命周期至少与返回引用的对象的生命周期一样长时才有意义(否则你将有一个悬空引用)。在这种情况下,返回一个指针似乎很好:

someResource

但你必须让它绝对清楚,调用者不会获得指针的所有权,也不应该尝试删除它。

答案 3 :(得分:5)

我可以想出几种方法来解决这个问题:

  • 正如其他人所建议的那样,请使用boost::optional
  • 使对象具有表明它无效的状态(Yuk!)
  • 使用指针代替参考
  • 拥有 null对象
  • 的类的特殊实例
  • 抛出异常以指示失败(并非总是适用)

答案 4 :(得分:2)

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

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

2 - 将参考作为out参数获取并返回布尔值以获得成功。

bool SomeClass::getSomething(std::string name, SomeResource& outParam) {
    if (content_.find(name) != content_.end()) 
    {
        outParam = content_[name];
        return true;
    }
    return false;
}

答案 5 :(得分: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;
};

答案 6 :(得分:1)

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

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

#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)会返回,在这种情况下由nullptr表示 - 您也可以使用0,参考&#39 ; s地址已设置。现在可以检查此地址,如果您一直在使用指针。

答案 7 :(得分: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)
}