【问题标题】:Why are references not "const" in C++?为什么 C++ 中的引用不是“const”?
【发布时间】:2016-10-28 22:17:41
【问题描述】:

我们知道一个“const 变量”表示一旦赋值就不能改变变量,像这样:

int const i = 1;
i = 2;

上面的程序将无法编译; gcc 提示错误:

assignment of read-only variable 'i'

没问题,我可以理解,但是下面的例子超出了我的理解范围:

#include<iostream>
using namespace std;
int main()
{
    boolalpha(cout);
    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;
    int const &ri = i;
    cout << is_const<decltype(ri)>::value << endl;
    return 0;
}

输出

true
false

很奇怪。我们知道,一旦一个引用被绑定到一个名称/变量,我们就不能改变这个绑定,我们改变它的绑定对象。所以我想ri的类型应该和i一样:当iint const时,为什么ri不是const

【问题讨论】:

  • 另外,boolalpha(cout) 非常不寻常。你可以改用std::cout &lt;&lt; boolalpha
  • ri 是一个与 i 无法区分的“别名”。
  • 这是因为一个引用总是绑定同一个对象。 i 也是一个参考,但由于历史原因,您不会以明确的方式声明它。因此i 是引用存储的引用,ri 是引用相同存储的引用。但是iri 在本质上没有区别。由于您无法更改引用的绑定,因此无需将其限定为const。让我声明@Kaz 评论比经过验证的答案要好得多(永远不要使用指针解释引用,ref 是名称,ptr 是变量)。
  • 好问题。在看到这个例子之前,我预计 is_const 在这种情况下也会返回 true。在我看来,这是为什么const 从根本上倒退的一个很好的例子;一个“可变”属性(一个 la Rust 的 mut)似乎会更加一致。
  • 或许标题应该改成“为什么is_const&lt;int const &amp;&gt;::value是假的?”或类似的;除了询问类型特征的行为之外,我很难看到这个问题的任何意义

标签: c++ reference constants language-lawyer decltype


【解决方案1】:

这似乎违反直觉,但我认为理解这一点的方法是认识到,在某些方面,引用被视为句法指针。

这对于 指针 来说似乎是合乎逻辑的:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const* ri = &i;
    cout << is_const<decltype(ri)>::value << endl;
}

输出:

true
false

这是合乎逻辑的,因为我们知道不是 指针对象 是 const(它可以指向别处),而是被指向的对象。

所以我们正确地看到指针本身的常量返回为false

如果我们想让 指针 本身 const 我们必须说:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const* const ri = &i;
    cout << is_const<decltype(ri)>::value << endl;
}

输出:

true
true

所以我认为我们看到了与 reference 的句法类比。

然而 references 在语义上与指针不同,尤其是在 一个 关键方面,一旦绑定,我们就不能重新绑定对另一个对象的引用.

所以即使 referencespointers 共享相同的语法,但规则是不同的,因此语言阻止我们声明 reference 本身@ 987654327@这样的:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const& const ri = i; // COMPILE TIME ERROR!
    cout << is_const<decltype(ri)>::value << endl;
}

我认为我们不允许这样做,因为当语言规则阻止 reference 以与 pointer相同的方式反弹时,似乎不需要这样做> 可以(如果没有声明const)。

所以回答这个问题:

Q) 为什么“引用”在 C++ 中不是“常量”?

在您的示例中,语法使被引用的事物const 与声明指针的方式相同。

无论对错,我们都不允许将 reference 本身设为const,但如果我们这样做,它会是这样的:

int const& const ri = i; // not allowed

Q) 我们知道,一旦引用绑定到名称/变量,我们就无法更改此绑定,我们会更改其绑定对象。 所以我想ri的类型应该和i一样:当iint const时,为什么ri不是const

为什么decltype() 没有转移到引用 绑定的对象?

我想这是为了与 pointers 的语义等价,也许decltype()(声明类型)的功能是回顾绑定之前声明的内容发生。

【讨论】:

  • 听起来你在说“引用不能是 const,因为它们总是 const”?
  • 这可能是真的“semantically绑定后对它们的所有操作都转移到它们引用的对象”,但 OP 期望这适用于 @ 987654341@也是,发现没有。
  • 这里有很多曲折的假设和可疑的指针类比,我认为当像sonyuanyao那样检查标准时,这会过度混淆问题,直截了当:参考是不是 cv 限定类型,因此它不能是 const,因此 std::is_const 必须返回 false。他们本可以使用意味着它必须返回 true 的措辞,但他们没有。而已!所有这些关于指针、“我假设”、“我假设”等的东西都没有提供任何真正的说明。
  • @underscore_d 这对于聊天来说可能是一个比这里的 cmets 部分更好的问题,但是你有没有从这种考虑引用的方式中“不必要的混淆”的例子?如果它们可以以这种方式实现,那么以这种方式思考它们有什么问题? (我不认为这个问题很重要,因为decltype 不是运行时操作,所以“在运行时,引用就像指针一样”的想法,无论正确与否,并不真正适用。
  • 引用在语法上也不会像指针一样处理。它们有不同的语法来声明和使用。所以我什至不确定你的意思。
【解决方案2】:

为什么“ri”不是“const”?

std::is_const 检查类型是否为 const 限定的。

如果 T 是 const 限定类型(即 const 或 const volatile),则提供等于 true 的成员常量值。对于任何其他类型,值为 false。

但是引用不能是 const 限定的。 References [dcl.ref]/1

Cv 限定的引用格式不正确,除非 cv 限定符 通过使用 typedef-name ([dcl.typedef], [temp.param]) 或 decltype-specifier ([dcl.type.simple]),在这种情况下 cv 限定符被忽略。

所以is_const&lt;decltype(ri)&gt;::value 将返回false,因为ri(引用)不是 const 限定类型。正如您所说,我们不能在初始化后重新绑定引用,这意味着引用始终是“const”,另一方面,const-qualified 引用或 const-unqualified 引用实际上可能没有意义。

【讨论】:

  • 直截了当,因此直截了当,而不是接受答案的所有胡扯假设。
  • @underscore_d 这是一个很好的答案,直到您认识到操作员也在询问原因。 “因为它是这么说的”对于问题的那个方面并没有真正的用处。
  • @user9999999 另一个答案也没有真正回答这个方面。如果引用不能被反弹,为什么is_const 不返回true?该答案试图类比指针如何可选地重新安装,而引用不是 - 这样做会导致自相矛盾,原因相同。我不确定这两种方式是否有真正的解释,除了那些编写标准的人有些武断的决定,有时这是我们所希望的最好的。因此有这个答案。
  • 我认为另一个重要方面是decltype 不是函数,因此直接作用于引用本身而不是作用于所指对象。 (这可能与“引用基本上是指针”的答案更相关,但我仍然认为这是使这个示例令人困惑的部分原因,因此值得在这里提及。)
  • 为了进一步阐明@KyleStrand 的评论:decltype(name) 的行为不同于一般的decltype(expr)。例如,decltype(i)i 的声明类型,即const int,而decltype((i)) 将是int const &amp;
【解决方案3】:

您需要使用std::remove_reference 来获取您正在寻找的价值。

std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;

如需了解更多信息,请参阅this post

【讨论】:

    【解决方案4】:

    为什么宏不是const?职能?字面意思?类型的名称?

    const 事物只是不可变事物的一个子集。

    由于引用类型就是这样——类型——可能需要在它们上加上const-限定符以与其他类型(尤其是指针类型)对称,但这很快就会变得非常乏味。

    如果默认情况下 C++ 具有不可变对象,需要在您想要成为 const 的任何对象上使用 mutable 关键字,那么这很容易:根本不允许程序员将mutable 添加到引用类型。

    事实上,它们是不可变的,没有条件。

    而且,由于它们不是 const-qualified,因此对于引用类型上的 is_const 产生 true 可能会更多感到困惑。

    我发现这是一个合理的折衷方案,尤其是因为不存在用于改变引用的语法这一事实,无论如何都会强制执行不变性。

    【讨论】:

      【解决方案5】:

      这是 C++ 中的一个怪癖/功能。尽管我们不认为引用是类型,但它们实际上“位于”类型系统中。虽然这看起来很尴尬(考虑到当使用引用时,引用语义会自动发生并且引用“不碍事”),但有一些合理的理由可以解释为什么引用在类型系统中建模而不是作为外部的单独属性输入。

      首先,让我们考虑并非声明名称的每个属性都必须在类型系统中。从C语言开始,我们有“存储类”和“联动”。可以引入一个名称为extern const int ri,其中extern 表示静态存储类和链接的存在。类型只是const int

      C++ 显然接受了表达式具有类型系统之外的属性的概念。该语言现在有一个“值类”的概念,它试图组织表达式可以展示的越来越多的非类型属性。

      然而引用是类型。为什么?

      曾经在 C++ 教程中解释过,像 const int &amp;ri 这样的声明将 ri 引入为具有类型 const int,但引用语义。那个引用语义不是一种类型。它只是一种属性,表示名称和存储位置之间的异常关系。此外,引用不是类型这一事实被用来解释为什么不能基于引用构造类型,即使类型构造语法允许这样做。例如,数组或指向引用的指针是不可能的:const int &amp;ari[5]const int &amp;*pri

      但实际上引用 类型,因此decltype(ri) 检索到一些不合格的引用类型节点。您必须下降到类型树中的此节点才能到达具有remove_reference 的基础类型。

      当您使用ri 时,引用被透明地解析,因此ri“看起来和感觉就像i”并且可以称为它的“别名”。不过,在类型系统中,ri 实际上确实有一个类型是“reference to const int”。

      为什么是引用类型?

      考虑如果引用是不是类型,那么这些函数将被认为具有相同的类型:

      void foo(int);
      void foo(int &);
      

      这根本不可能是出于不言而喻的原因。如果它们具有相同的类型,则意味着任一声明都适用于任一定义,因此每个 (int) 函数都必须被怀疑引用。

      同样,如果引用不是类型,那么这两个类声明将是等价的:

      class foo {
        int m;
      };
      
      class foo {
        int &m;
      };
      

      一个翻译单元使用一个声明,而同一程序中的另一个翻译单元使用另一个声明是正确的。

      事实上,引用意味着实现上的差异,并且不可能将其与类型分开,因为 C++ 中的类型与实体的实现有关:它的“布局”在位可以这么说。如果两个函数具有相同的类型,则可以使用相同的二进制调用约定来调用它们:ABI 是相同的。如果两个结构或类具有相同的类型,则它们的布局以及访问所有成员的语义相同。引用的存在改变了类型的这些方面,因此将它们合并到类型系统中是一个简单的设计决策。 (但是,请注意这里的反驳:结构/类成员可以是static,这也会改变表示;但这不是类型!)

      因此,引用在类型系统中是“二等公民”(与 ISO C 中的函数和数组不同)。有些事情我们不能用引用“做”,例如声明指向引用的指针或它们的数组。但这并不意味着它们不是类型。它们只是不是有意义的类型。

      并非所有这些二等限制都是必不可少的。鉴于存在引用结构,可能存在引用数组!例如

      // fantasy syntax
      int x = 0, y = 0;
      int &ar[2] = { x, y };
      
      // ar[0] is now an alias for x: could be useful!
      

      这只是没有在 C++ 中实现,仅此而已。但是,指向引用的指针根本没有意义,因为从引用中提取的指针只是指向被引用的对象。没有引用数组的可能原因是,C++ 人认为数组是从 C 继承的一种低级特性,在许多方面都被破坏,无法修复,他们不想将数组作为任何新事物的基础。但是,引用数组的存在将清楚地说明引用必须是类型。

      const-qualifiable 类型:也在 ISO C90 中找到!

      一些答案​​暗示引用不采用const 限定符这一事实。那是一条红鲱鱼,因为声明 const int &amp;ri = i 甚至都没有尝试进行 const 限定的引用:它是对 const 限定类型的引用(它本身不是 @ 987654345@)。就像 const in *ri 声明了一个指向 const 的指针,但该指针本身不是 const

      也就是说,引用本身不能携带const 限定符。

      然而,这并不奇怪。即使在 ISO C 90 语言中,也不是所有类型都可以是 const。即不能是数组。

      首先,声明 const 数组的语法不存在:int a const [42] 是错误的。

      然而,上述声明试图做的事情可以通过一个中间typedef来表达:

      typedef int array_t[42];
      const array_t a;
      

      但这并没有像它看起来那样做。在这个声明中,获得const 资格的不是a,而是元素!也就是说,a[0] 是一个const int,但a 只是“int 数组”。因此,这不需要诊断:

      int *p = a; /* surprise! */
      

      这样做:

      a[0] = 1;
      

      再次强调,引用在某种意义上是类型系统中的“第二类”,就像数组一样。

      请注意这个类比是如何更深入的,因为数组也有一个“不可见的转换行为”,比如引用。程序员不必使用任何显式运算符,标识符a 自动变成int * 指针,就好像使用了表达式&amp;a[0]。这类似于引用ri,当我们将其用作主要表达式时,它神奇地表示它所绑定的对象i。这只是另一个“衰减”,就像“数组到指针衰减”。

      就像我们不能被“指向指针的数组”迷惑而错误地认为“数组只是 C 和 C++ 中的指针”一样,我们同样不能认为引用只是没有类型的别名他们自己的。

      decltype(ri) 抑制通常的引用到其所指对象的转换时,这与sizeof a 抑制数组到指针的转换并在数组类型本身上进行操作并没有太大区别 计算它的大小。

      【讨论】:

      • 这里有很多有趣和有用的信息 (+1),但它或多或少受限于“引用在类型系统中”这一事实——这并不完全回答OP的问题。在这个答案和@songyuanyao 的答案之间,我想说有足够的信息来了解情况,但感觉像是答案的必要背景,而不是完整的答案。
      • 另外,我认为这句话值得强调:“当你使用 ri 时,引用被透明地解析......” 一个关键点(我在 cmets 中提到过,但到目前为止还没有t 出现在任何答案中)是 decltype 执行此透明分辨率(它不是一个函数,因此 ri 在您描述的意义上不是“使用”的)。这非常适合您对类型系统的全部关注——它们的关键联系是decltype类型系统操作
      • 嗯,关于数组的东西感觉有点切题,但最后一句话很有帮助。
      • .....虽然在这一点上我已经支持你并且我不是 OP,所以你并没有真正通过听我的批评获得或失去任何东西......
      • 显然,一个简单(且正确的答案)只是将我们指向标准,但我喜欢这里的背景思想,尽管有些人可能会争辩说其中的一部分是假设。 +1。
      【解决方案6】:

      const X& x” 表示 x 为 X 对象取别名,但不能通过 x 更改该 X 对象。

      看看std::is_const

      【讨论】:

      • 这甚至没有试图回答这个问题。
      猜你喜欢
      • 2018-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-17
      • 1970-01-01
      • 2020-04-25
      • 2019-06-26
      • 2020-07-20
      相关资源
      最近更新 更多