为什么C ++中的引用不是“const”?

问题描述 投票:85回答:6

我们知道“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

c++ reference const language-lawyer decltype
6个回答
52
投票

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

这似乎是指针的逻辑:

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

所以我认为我们看到了与参考文献的句法类比。

但是,引用在语义上与指针不同,特别是在一个关键方面,我们不允许在绑定后重新绑定对另一个对象的引用。

因此,即使引用与指针共享相同的语法,规则也是不同的,因此语言阻止我们像这样声明引用本身const

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

我假设我们不允许这样做,因为当语言规则阻止引用以与指针相同的方式反弹时(如果它未被声明为const),似乎不需要它。

所以回答这个问题:

问)为什么“引用”在C ++中不是“const”?

在您的示例中,语法使得引用const的内容与声明指针时的方式相同。

无论是对还是错,我们都不允许自己制作引用const,但如果我们这样,它将如下所示:

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

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

为什么qazxsw poi没有转移到引用所绑定的对象?

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


53
投票

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

decltype()检查类型是否为const限定。

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

但是引用不能是const限定的。 std::is_const

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

所以References [dcl.ref]/1将返回is_const<decltype(ri)>::value因为false(参考)不是const限定类型。正如你所说,我们不能在初始化后重新引用引用,这意味着引用总是“const”,另一方面,const限定引用或const不合格引用实际上可能没有意义。


18
投票

你需要使用ri来获得你正在寻找的价值。

std::remove_reference

有关更多信息,请参阅std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;


6
投票

为什么宏不是this post?功能?文字?类型的名称?

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

由于引用类型只是 - 类型 - 它可能在某些意义上要求它们上的const-qualifier与其他类型(特别是指针类型)的对称性,但这将非常繁琐非常繁琐。

如果C ++默认使用不可变对象,那么在你不想成为const的任何东西上需要mutable关键字,那么这很容易:只是不允许程序员将const添加到引用类型。

事实上,它们是不可改变的。

并且,由于它们不是mutable合格的,因此参考类型的const可能会更加令人困惑。

我发现这是一个合理的妥协,特别是因为不变性无论如何都是由于没有语法存在来改变引用。


5
投票

这是C ++中的一个怪癖/特性。虽然我们不认为引用是类型,但它们实际上“坐”在类型系统中。虽然这看起来很尴尬(假设在使用引用时,引用语义自动发生并且引用“不在路上”),有一些可辩护的原因可以解释为什么引用在类型系统中建模而不是作为一个单独的属性。类型。

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

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

但参考文献是类型。为什么?

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

但实际上引用是类型,因此const int &*pri检索一些不合格的引用类型节点。您必须在类型树中经过此节点,才能使用decltype(ri)获取基础类型。

当你使用remove_reference时,引用被透明地解析,因此ri“看起来和感觉像ri”并且可以被称为“别名”。然而,在类型系统中,i实际上确实具有“引用ri”的类型。

为什么引用类型?

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

const int

这根本不是出于几乎不言而喻的原因。如果它们具有相同的类型,则意味着要么声明适合于任一定义,所以每个void foo(int); void foo(int &); 函数都必须被怀疑参考。

类似地,如果引用不是类型,那么这两个类声明将是等效的:

(int)

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

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

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

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

static

这只是在C ++中实现的,就是这样。但是,引用的指针根本没有意义,因为从引用中提取的指针只会转到引用的对象。没有引用数组的可能原因是C ++人员认为数组是一种从C继承的低级特性,它在很多方面都是无法修复的,并且它们不希望触及数组作为什么新的基础。但是,引用数组的存在将清楚地说明引用必须是何种类型。

// fantasy syntax int x = 0, y = 0; int &ar[2] = { x, y }; // ar[0] is now an alias for x: could be useful! 可定义类型:也可在ISO C90中找到!

一些答案暗示了引用不采用const限定符的事实。这是一个红色的鲱鱼,因为声明const甚至没有尝试制作一个const int &ri = i限定的引用:它是一个const限定类型的引用(它本身不是const)。就像const声明指向const in *ri的指针,但指针本身不是const

也就是说,参考不能自己携带const限定符。

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

首先,声明const数组的语法不存在:const是错误的。

但是,上述声明试图做的事情可以通过中间int a const [42]来表达:

typedef

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

a

这样做:

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

同样,这强调了这样一种观点,即类型系统中的引用在某种意义上是“第二类”,就像数组一样。

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

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

i抑制通常将引用转换为其引用对象时,这与decltype(ri)抑制数组到指针转换并且对数组类型本身进行操作以计算其大小没有太大区别。


-5
投票

const X&x“表示X对象为X对象,但不能通过x更改该X对象。

并看到sizeof a

© www.soinside.com 2019 - 2024. All rights reserved.