【问题标题】:This code appears to achieve the return of a null reference in C++此代码似乎实现了在 C++ 中返回空引用
【发布时间】:2011-02-23 02:32:32
【问题描述】:

我的 C++ 知识有些零碎。我在工作中修改了一些代码。我更改了一个函数以返回对类型的引用。在内部,我根据传入的标识符查找对象,如果找到,则返回对该对象的引用。当然,我遇到了如果找不到对象要返回什么的问题,并且在浏览网络时,许多人声称在 C++ 中返回“空引用”是不可能的。根据这个建议,我尝试了返回成功/失败布尔值的技巧,并使对象引用成为输出参数。但是,我遇到了需要初始化作为实际参数传递的引用的障碍,当然没有办法做到这一点。我退回到只返回一个指针的通常方法。

我问过一位同事。他经常使用以下技巧,最近版本的 Sun 编译器和 gcc 都接受了这一技巧:

MyType& someFunc(int id)
{
  // successful case here:
  // ...

  // fail case:
  return *static_cast<MyType*>(0);
}

// Use:

...
MyType& mt = somefunc(myIdNum);

if (&mt) // test for "null reference"
{
  // whatever
}
...

我已经维护这个代码库有一段时间了,但我发现我没有足够的时间来查找我想要的语言的小细节。我一直在翻阅我的参考书,但这本书的答案却让我难以捉摸。

现在,几年前我有一个 C++ 课程,其中我们强调在 C++ 中一切都是类型,所以我在思考问题时尽量记住这一点。解构表达式:“static_cast>(0);”,在我看来,我们确实采用了文字零,将其转换为指向 MyType 的指针(这使其成为空指针),然后在分配给引用类型(返回类型)的上下文中应用解引用运算符,这应该给我一个对指针指向的同一对象的引用。这肯定看起来像返回一个空引用给我。

任何解释为什么这有效(或为什么不应该)的建议将不胜感激。

谢谢, 查克

【问题讨论】:

标签: c++


【解决方案1】:

此代码不起作用,尽管它可能看起来有效。此行取消引用空指针:

return *static_cast<MyType*>(0);

零,转换为指针类型,产生一个空指针;然后使用一元-* 取消引用这个空指针。

取消引用空指针会导致未定义的行为,因此您的程序可以做任何事情。在您描述的示例中,您获得了“空引用”(或者,您似乎获得了空引用),但您的程序崩溃或发生其他任何事情也是合理的。

【讨论】:

  • 一元 * 运算符不会取消引用指针。来自 n2521(我知道草稿,但我确信它在 t 最新版本中是相同的)。第 5.3.1-1 节:一元 * 运算符的结果是一个引用对象或函数的左值。这就是为什么正式名称是“一元 * 运算符”而不是“解引用运算符”
  • @Martin York:我知道标准不会调用* derefence 但不是“为指针指向的对象形成左值”什么大多数人的意思是取消防御?在标准中关于“unary *”的注释中,它说“可以取消引用指向不完整类型的指针”,这似乎意味着应用 unary * 是执行取消引用的一种方式。
  • 我认为@Martin 的观点是它不一定访问 指向的值。在这种情况下,它不会因为访问冲突/段错误而崩溃,就像空指针真的被取消引用时那样。它只是默默地通过,因为空指针从未被取消引用。相反,创建了一个左值,它引用了一个不存在的对象。
  • @Martin:我认为“取消引用”和“执行间接”具有相同的含义,并且不需要使用间接结果来进行取消引用。 K&R 2ed p.94:“一元运算符 *indirectiondereferencing 运算符。” Stroustrup TC++PL 3ed p.88:“对指针的基本操作是 dereferencing,即引用指针指向的对象。这也称为 indirection我>。”我认为取消引用不需要使用。
  • @Martin:我看不出在 C 和 C++ 中定义一元 * 的方式有任何实际区别(当然,我在 the other question 中指出的区别除外)。 C99 §6.5.3.2/4 说“结果是指定对象的左值”。 C++03 §5.3.1/1 说“结果是一个引用对象的左值”。在这种情况下,我看不到“指定”和“参考”之间的语义差异。我浏览了我能找到的每一本相关的 C++ 书籍,它们都交替使用“间接”和“取消引用”。
【解决方案2】:

return *static_cast&lt;MyType*&gt;(0); 行取消引用导致未定义行为的空指针。

【讨论】:

    【解决方案3】:

    这是未定义的行为。因为它是未定义的行为,它可能在您当前的编译器上“工作”,但如果您升级/更改编译器,它可能会中断。

    来自 C++03 规范:

    8.3.2/4 ... 应初始化引用以引用有效的对象或函数。 [注意:特别是,空引用不能存在于定义良好的程序中,因为创建此类引用的唯一方法是将其绑定到通过取消引用空指针获得的“对象”,这会导致未定义的行为。

    【讨论】:

      【解决方案4】:

      如果您与那个返回引用接口结婚,那么 Right Thing® 如果找不到给定 ID 的对象,将引发异常。至少这样你的可怜的用户可以用一个catch来捕获条件。

      如果你去引用它们上的空指针,它们没有定义的方法来处理错误。

      【讨论】:

        【解决方案5】:

        正如其他人所提到的,您的代码是错误的,因为它取消了对空指针的引用。

        其次,您错误地使用了引用返回类型,在您的示例中返回引用通常在 C/C++ 中不好,因为它违反了所有对象都由指向内存地址的指针引用的语言的内存管理模型.如果您将使用指针编写的 C/C++ 代码重写为使用引用的代码,您最终会遇到这些问题。使用指针的代码可以返回 NULL 指针而不会导致问题,但是在返回引用时,您必须返回可以转换为 0 或 false 语句的内容。

        最重要的是错误代码仅在异常情况下执行的模式,在本例中是“失败情况”。不正确的错误处理、带有错误的错误记录等是计算机系统中最灾难性的错误,因为它们永远不会出现在快乐的情况下,但当某些事情不遵循正常流程时总是会导致系统崩溃。

        确保您的代码正确的唯一方法是让测试用例具有 100% 的覆盖率,这意味着还要测试错误处理,在您的示例中这可能会导致您的程序出现分段错误。

        【讨论】:

        • 返回对对象的引用并不少见。它不是需要的,但这并不意味着你应该在任何不是重载运算符的地方使用指针。
        • 我建议不要返回引用只是因为你不应该使用它,除非你知道它是如何工作的。不必要的对象构造、赋值和销毁所隐含的开销仅仅是因为返回引用的函数总是必须用对现有对象的引用来调用,即使不使用返回值也是如此,它是如此之大以至于它是公平的,因此建议人们应该除非他/她完全理解其后果,否则避免这种情况。
        • @Ernelli, -1 建议使用指针而不是引用。你在评论中在说什么?什么开销?
        • @avakar,我的建议纯粹与 Chucks 示例有关,而不是一般参考。我已经编辑了我的回复以反映这一点。关于开销,我的意思是在调用返回引用的函数并且省略返回值时确保左值包含有效引用的开销。
        • @Ernelli,我删除了反对票。您能否进一步澄清开销思考?我还是没听懂。
        【解决方案6】:

        嗯,你在问题的标题中自己说过。代码似乎使用空引用。

        当人们说 C++ 中不存在空引用时,这并不意味着如果您尝试创建空引用,编译器会生成错误消息。正如您所发现的,没有什么可以阻止您从空指针创建引用。

        这只是意味着 C++ 标准没有指定如果你这样做会发生什么。空引用是不可能的,因为 C++ 标准规定不能从空指针创建引用。 (但没有说明如果您尝试这样做会产生编译错误。)

        这是未定义的行为。实际上,由于引用通常以指针的形式实现,因此如果您尝试从空指针创建引用,它通常似乎可以工作。但是没有保证它会起作用。或者明天它会继续工作。这在 C++ 中是不可能的,因为如果你这样做,C++ 指定的行为将不再适用。您的程序可能会做任何事情

        这听起来可能有点假设,因为嘿,它似乎工作得很好。但是请记住,仅仅因为它有效,而且它有效,当天真的编译时,它可能会在编译器尝试应用一些优化或其他时中断。当编译器看到一个引用时,语言规则保证它不为空。那么如果编译器使用这个假设来加速代码,而你在它背后创建一个“空引用”会发生什么?

        【讨论】:

          【解决方案7】:

          我同意其他发帖者的观点,即您的示例的行为未定义,确实不应该使用。我在这里提供了一些替代方案。各有优劣

          1. 如果找不到对象,则抛出异常,该异常被调用层捕获。
          2. 创建一个可全局访问的MyType 实例,它是一个简单的shell 对象(即static const MyType BAD_MYTYPE),可用于表示一个坏对象。
          3. 如果可能不会经常找到对象,则可以通过引用将对象作为参数传入并返回 bool 或其他指示成功/失败的错误代码。如果它找不到对象,你就不要在函数中分配它。
          4. 改用指针并在返回时检查 0。
          5. 使用允许检查返回对象的有效性的 Boost 智能指针。

          我个人偏好前三个中的一个。

          【讨论】:

            【解决方案8】:

            正如其他人已经说过的那样,不幸的是,该代码似乎只能工作......但是,更根本的是,您在这里违反了合同。

            在 C++ 中,引用是对象的别名。根据您的函数的签名,我永远不会期望传递一个“NULL”引用,我当然不会在使用它之前对其进行测试。正如 James McNellis 所说,创建 NULL 引用是未定义的行为,但是在大多数情况下,这种行为只会在您实际尝试使用它时才会出现,并且您现在将您的方法的用户暴露在令人讨厌/棘手的问题中。

            我不会在这个问题上进一步讨论,只是在这个问题上指向Herb Sutter's pick

            现在解决您的问题。

            显而易见的解决方案当然是一个普通的指针。人们期望一个指针可能为空,所以当它返回给他们时他们会测试它(如果他们不这样做,那是他们懒惰的错误)。

            还有其他选择,但它们主要归结为有一个特殊的值来指示你的失败,并且仅仅为了它而使用复杂的设计没有多大意义......

            最后一个选择是在这里使用异常,但我自己偏爱这个建议。异常适用于您看到的异常情况,对于find/search 功能,它实际上取决于预期结果:

            • 如果您正在实现一些内部工厂,您在其中注册模块,然后稍后再调用它们,那么无法检索一个模块将表明程序中存在错误,因此被异常报告是可以的
            • 如果您正在为您的数据库实现搜索引擎,从而处理用户输入,那么很可能会出现找不到匹配输入条件的结果,因此在这种情况下我不会使用异常,因为它是不是编程错误,而是完全正常的课程

            注意:其他方式包括

            • Boost.Optional,虽然我觉得用它来包装引用很笨拙
            • 一个共享指针或弱指针,用于控制/监视对象的生命周期,以防它在您仍在使用时可能被删除……但在多线程环境中,监视本身不起作用
            • 一个标记值(通常声明为静态常量),但它仅在您的对象具有有意义的“坏”或“空”值时才有效。这当然不是我推荐的方法,因为你再一次给了一个对象,但是如果他们对它做任何事情,它就会在用户手中爆炸

            【讨论】:

              猜你喜欢
              • 2013-01-24
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多