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

Posted

技术标签:

【中文标题】在 C++ 中返回“NULL 引用”?【英文标题】:Returning a "NULL reference" in C++? 【发布时间】:2012-05-09 09:49:47 【问题描述】:

javascriptphp 等动态类型语言中,我经常执行以下功能:

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++ 特定的模式?任何有关正确方法的建议将不胜感激。

【问题讨论】:

“除了使用指针”是否排除智能指针? 返回迭代器是一种经常出现的模式。如果未找到该事物,则返回无效的迭代器。 你的建议 isNull() 不是典型的 C++。您要解决的真正问题是什么。如果我们了解您想要做什么,也许我们可以提供更好的想法。 你为什么要有效地问“解决这个问题的方法是什么,除了解决它的正确方法”?为什么你不想使用指针来解决用例指针打算解决的问题? 编辑了您的问题以添加有关该问题的注释,让读者清楚为什么您要使用参考文献进行此操作 【参考方案1】:

在引用期间不能这样做,因为它们永远不应该为 NULL。基本上有三种选择,一种使用指针,另一种使用值语义。

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

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

    使用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);  
    
    

    使用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::optional 相对于std::pair 的主要优点是未初始化的boost::optional 值不会构造其封装的类型。这意味着它适用于没有默认构造函数的类型,并为具有非平凡默认构造函数的类型节省时间/内存。

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

【讨论】:

+1:当然,如果不需要增强依赖,推出自己的“可选”类并不太难。 @Troubadour:实际上很难推出自己的可以容纳任何类型的“可选”;您需要嵌入一个具有正确大小和对齐类型的 POD 对象,这一点都不简单。 (当然,如果将其限制为默认可构造类型会容易得多,在这种情况下,它与选项 2 基本相同)。 不错的答案。在 C++11 中,还可以返回 std::unique_ptr 或 std::shared_ptr。前者对应于值语义,因为必须复制资源,而后者对应于选项 1(但可能还需要重构 SomeClass 的 content_ 以包含 shared_ptrs)。无论如何,在这两种情况下,内存管理都比选项 1 更清晰。【参考方案2】:

为什么“除了使用指针”?使用指针在 C++ 中的方式。除非您定义一些“可选”类型,它具有您提到的 isNull() 函数之类的东西。 (或使用现有的,例如boost::optional

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

【讨论】:

关于“引用的设计和保证是永远不会为空。”我还没有遇到一个保证引用不为空的实现。该标准不保证引用永远不会为空。这是一项授权,而不是保证。负担在于程序员。不幸的是,进行空引用非常容易(并且高度 UB)。但是+1。如果您想要一个可能为 null 的“引用”,那么要做的是使用指针而不是引用。 @DavidHammen:标准几乎说明了我在上面所做的:引用指向一个对象。这样做时,该标准保证引用指向有效定义良好的 C++ 代码中的对象。当然,您可以欺骗编译器创建“空引用”,但是程序不再受标准指定的行为的约束,并且保证不适用。取决于你的观点,真的。 :) 该标准不保证在展示 UB 的程序中引用是非空的。但它确实保证它们在定义明确的程序中是非空的。 :) @jalf:我们在语义上争论不休,但我仍然喜欢“授权”这个词而不是“保证”。对我来说,“保证”是编译器供应商的负担,而“授权”是程序员的负担。 @jalf:欺骗编译器创建 NULL 引用 (int &amp;n = *static_cast&lt;int*&gt;(NULL);) 并不难。但是,我绝不会建议这是一个好主意。 :) @DavidHammen 在某种程度上,你是对的。但实际上有两种看待它的方式。没有什么能阻止程序员尝试定义“空引用”,标准也没有禁止它。它没有强制任何东西。它只是说“如果你这样做,我不会保证你程序的行为”。换句话说,“在任何尊重我的规则的程序中,我保证引用不会为空”。但正如你所说,这是毫无意义的狡辩。【参考方案3】:

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

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

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

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

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

【讨论】:

我很喜欢这种方式,我去看看。 IMO 有点复杂 - 使用 @jalf 所说的指针,不需要额外的库和完美的 c++。 optional 需要值语义,即返回的是一个副本(我过去是通过引用返回的,但不知道是不是ub!) @Nim 我同意,在问题标题的上下文中它可能更简单。问题的主体表明 OP 还需要一种方法来检查返回值的有效性。另外,当返回指针时,必须绝对确定所有权是明确定义的。 boost.optional 是否比使用 unique_ptr 和 shared_ptr 有很多优势?它们都可以测试 nullptr 以查看它们是否无效。 @Benj 确切地说,问题是如何让调用者知道一个值是否有效。【参考方案4】:

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

按照其他人的建议,使用boost::optional 使对象具有表明它无效的状态(Yuk!) 使用指针代替引用 有一个特殊的类实例 空对象 抛出异常以指示失败(并不总是适用)

【讨论】:

对反对票进行解释会很好。特别是对于有多个建议的东西。 :) 我打算建议:有一个类的特殊实例,它是空对象。有点像 C# 使用 String.Empty 的方式。每个类只有一个“null”版本的静态实例。然后您可以将返回的值与“null”实例进行比较。 @Neil 是的,在某些情况下它可能是一种方便的方法。就我个人而言,我不喜欢它:一旦你使用它,你的代码就不能(简单地)假设你手头的对象是有效的。我上面的第二个建议也是如此。 终于有人推荐了一个空对象设计模式。那真的应该是第一位的。 我虽然关于“有一个作为空对象的类的特殊实例”然后遇到了这个。【参考方案5】:

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

【讨论】:

shared_ptr 只能在指针可以比创建它的对象寿命长的情况下使用。 在这种情况下,outParam 不是常量,允许用户为每个人修改它,因此它具有与传递 const 引用不同的功能。【参考方案6】:

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

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

#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。现在可以像使用指针一样检查此地址。

【讨论】:

在 Xcode 6.3 上这永远不会起作用:如果 (!&ra),引用的地址将始终评估为 false。【参考方案7】:

这里有几个想法:

备选方案 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;
;

【讨论】:

【参考方案8】:

还有另一种选择——我不时使用的一种选择,当您真的不希望返回“空”对象而是“空/无效”对象时会这样做:

// 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)

【讨论】:

以上是关于在 C++ 中返回“NULL 引用”?的主要内容,如果未能解决你的问题,请参考以下文章

我用win7系统,无法获取未定义或null引用的属性“options”

java开发 怎么判断list集合中的元素全为null

js判断数据类型

Error-JavaScript-SCRIPT5007: 无法获取未定义或 null 引用的属性“style”

加密 URL 参数返回 NULL 问题

使用dynamic引发的异常:无法对 null 引用执行运行时绑定