为什么C++中没有“空引用”?

10
我正在阅读C++ FAQ - "8.6 - 何时使用引用,何时使用指针?",尤其是这句话:

如果可以使用引用,则使用引用;如果必须使用指针,则使用指针。

...

上述规则的例外情况是:函数的参数或返回值需要一个“sentinel”引用——一个不引用对象的引用。通常最好通过返回/取一个指针,并将NULL指针赋予特殊意义(引用必须始终别名对象,而不是解除引用的NULL指针)来实现这一点。

从我的经验来看,“sentinel”引用的需求确实经常是使用指针而不是引用的原因。我想知道的是:为什么C++没有专门的“NULL值”用于引用?这似乎会使指针几乎变得不必要,从而解决许多问题。
那么,为什么它不是语言规范的一部分呢? 编辑:

我不确定我的问题是否清晰——我猜我并非在字面上询问关于NULL引用。大多数情况下,我读到的是在C++中“引用就是对象”。而且,在大多数面向对象编程语言中,对象可以为NULL——如Pascal、C#、Java、JavaScript、PHP等,你可以使用someObject = nullsomeObject := nil。实际上,Pascal也支持指针,但仍允许对象为nil,因为它有其用途。那么,为什么C++特别之处在于没有NULL对象?这只是一个疏忽还是一个实际的决定?


1
请告诉我,这如何消除指针的需求? - Nathan White
3
因为这样引用就会成为指针的语法糖,可以在需要指针的任何地方使用。我个人认为这会完全消除引用的一些优点。 - user743382
5个回答

15

引用(Reference)意味着指向一个永远不会改变的有效内存地址,也就是解除引用是安全/定义的,并且不需要进行 NULL 检查。设计上,引用不能被重新分配。

当变量可以为 NULL 且客户端代码必须处理该情况时,应使用指针。当您可以保证有一个有效/已初始化的内存地址时,应使用引用。

将指针作为类的成员之一来存储对某个实例的“引用”是一种使用指针的示例,而此实例在类构造时可能未知或无法初始化。但是,成员引用必须在构造时通过初始化列表进行初始化,它们的赋值不能被推迟。

如果允许一个空引用,那么它和指针没有区别(除了语法),需要执行相同的空检查操作。

更新:

“而且,在大多数面向对象编程语言中,对象可能为 NULL——Pascal、C#、Java、JavaScript、PHP等。……因此,为什么C++在这方面特殊,没有NULL对象?这只是一个疏忽还是一个实际决定?”

我认为你对此有点困惑。Java 和 C# 等可能会给人“NULL 对象”的印象,但这些对象引用更像是带有简单语法、GC 机制和异常抛出的 C++ 指针。在这些语言中,如果您操作“Null Object”,将会得到某种异常,如 NullReferenceException(C#)。在 Java 中,它被称为 NullPointerException

在使用它们之前,您必须检查是否为 null 才能安全使用它们。就像 C++ 指针一样(除了在大多数托管语言中,指针默认初始化为 NULL,而在 C++ 中,通常由您负责设置初始指针值(否则未定义/使用已存在的内存))。

C++ 的观点是相当冗长的,因为它提供了选择:

  • 使用普通指针,按需执行必要的空检查。
  • 使用具有编译器强制执行有效性语义/约束的引用。
  • 自己编写智能指针,实现记录和任意行为。
  • 如有需要,小心地使用void指针引用未类型化的内存块。

  • 2
    Nit: 一个空指针和一个未初始化的指针是两个不同的概念。一个未初始化的指针无法被可靠地检测到,因为它可以指向任何地方,包括一个有效的对象。 - user743382
    @hvd 谢谢并修复。我所说的未初始化实际上是指 NULL。添加了一个构造函数部分,以说明指针的一个区别性用途。 - Preet Kukreti

    6
    请查看指针和引用之间的差异-虽然标准没有明确规定引用的实现方式,但目前它们总是作为指针实现。这意味着它们之间的主要区别是a) 语义 b) 指针可以被重新设置 c) 指针可以为空。
    所以简短的答案是,这是有意为之的。当你作为程序员看到一个引用时,你应该知道a)那个引用已经被填充了b)它不会改变(并且c)你可以使用它与对象相同的语义)。
    如果标准允许空引用,你就必须在使用引用之前始终检查null,这是不希望发生的。
    关于你的编辑:
    关于你的编辑,我想这里的混淆可能源于大多数更简单的面向对象语言隐藏了正在发生的事情。以Java为例,虽然看起来像您拥有NULL对象,并且可以对其进行分配,但实际上您不能-真正正在发生的是Java只有指针,并且可以将 null 值分配给这些指针。由于在Java中实际上不可能直接拥有对象,因此他们取消了指针语义并将指针视为对象。C++更强大,但也更容易出错(Java爱好者会说不需要堆栈用户类实例,在Java中不拥有它们的决定是为了减少复杂性,使Java更易于使用)。由此得出结论,由于Java没有对象,因此它没有引用。真正没有帮助的是,Java将C++人称为指针按值传递的引用传递。

    我已经更新了我的问题。你如何解释几乎所有面向对象的语言都允许使用null引用,但C++不允许?在所有这些其他语言中,你并不总是需要检查NULL,这完全取决于上下文。 - laurent
    3
    我不懂太多的OO语言,但对于我所知道的这些语言,我持不同意见。Java甚至没有引用(reference),Java只有指针(pointer)(例如,可以为null,可以被重新分配内存),尽管语法不同。Java没有引用的原因之一是因为Java也没有堆栈变量(除了内置类型) - 一切都在堆(heap)中,每个变量都是一个指针,因此可以为null,没有堆栈内存,也不需要引用。VBA也是如此。这就是我所知道的大部分OO语言了。 - Cookie
    2
    考虑C++中引用的典型用例:您有一些大型堆栈分配变量,并希望通过引用而不是值传递它们。在这里,您需要使用真正的C++引用,并且这样做是正确的,因为它们必须是非空的并且不会更改。在Java中,您无法拥有堆栈变量,因此调用环境始终只有指针,因此您不需要构造来按引用传递。经常存在混淆- Java人所谓的“按引用传递”,C++人实际上应该称之为“按指针传递”。 - Cookie

    1

    引用应该与另一个变量或对象相关联。因此,空引用或null引用有点违反了它的存在目的。

    从技术上讲,这意味着您从一个null引用开始,然后将其分配给某个变量,或者可能稍后重新分配给另一个变量。这根本不是引用被创建的初衷。

    指针有几个其他用途,引用无法简单地复制。例如,使用指针引用一大块复杂数据(一系列字节),并仅使用8个字节或更少的指针将其传递给需要频繁使用的位置。您不能使用引用来做到这一点,否则会浪费相同的内存。

    引用始终与类型相关联,而指针则不是这种情况。


    前两段是正确的。最后一段没有意义。 - Kos
    我肯定有一个使用案例,可能在这里没有被正确阐述。但是上面的评论没有任何阐述或意见,仅仅是“没有意义”,甚至更加无用,也没有任何分量。 - fkl

    1

    C++中的引用并不是大多数其他语言使用该术语的引用;它更像是一个别名。使用C++引用不涉及以与解除指针引用相同的方式解除其引用;它有效地是分配给它的对象。

    实际上并不存在空对象实例,因此您无法创建指向这种对象的C++引用。在其他语言中,空引用等效于C++空指针;它实际上并不包含任何内容。

    现在,关于您的其他想法:1. 拥有不可为空的引用对我来说是一件好事;这意味着您可以获得通过引用传递的所有好处,而无需在各个地方检查空值。2. 可空引用不能替代指针...如果您需要在低级别执行内存或IO工作,则需要原始访问内存和内存映射设备。您可以查看C#(或C++/CLR),它具有托管引用和非托管指针,正是出于这个原因。


    0

    引用必须指向某个对象,因此,空引用是不可用的。
    C语言没有引用,我们只能在这种语言中使用指针,因此,这与C语言兼容。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接