为什么在C ++中引用不是“const”?
我们知道一个“constvariables”表示一旦赋值,就不能更改variables,如下所示:
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
奇怪的。 我们知道,一旦引用绑定到一个名称/variables,我们不能改变这个绑定,我们改变它的绑定对象。 所以我想ri
的types应该和i
:当i
是一个int const
,为什么ri
不是const
?
这看起来可能是违反直觉的,但我认为理解这一点的方法是认识到,在某些方面, 引用在语法上像对待指针一样处理。
这似乎是一个指针的逻辑:
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
)以同样的方式重新引用引用时,它似乎并不需要。
所以要回答这个问题:
Q)为什么“引用”不是C ++中的“const”?
在你的例子中,语法使得被引用的const
的方法和你声明一个指针的方法相同 。
无论是正确还是错误,我们都不允许将参考本身设为const
但是如果我们是这样的话,将会看起来像这样:
int const& const ri = i; // not allowed
Q)我们知道一次引用绑定到一个名称/variables,我们不能改变这个绑定,我们改变它的绑定对象。 所以我想
ri
的types应该和i
:当i
是一个int const
,为什么ri
不是const
?
为什么decltype()
没有传递给引用绑定的对象呢?
我认为这是与指针的语义等价,也许decltype()
(函数的声明)的function是回顾绑定发生之前声明的内容。
为什么“ri”不是“const”?
std::is_const
检查types是否是const限定的。
如果T是一个const限定types(即const或const volatile),则提供成员常数值等于true。 对于任何其他types,价值是假的。
但是引用不能是const限定的。 $ 8.3.2 / 1参考文献[dcl.ref]
除非通过使用typedef-name([dcl.typedef],[temp.param])或decltype-specifier([dcl.type.simple])引入cv限定符,否则cv限定的引用是格式不正确的在这种情况下,cv-qualifiers被忽略。
所以is_const<decltype(ri)>::value
将返回false
因为引用ri
不是一个const限定的types。 正如你所说的,我们不能在初始化之后重新绑定引用,这意味着引用总是“const”,另一方面,const限定引用或者const限定引用实际上可能是没有意义的。
你需要使用std::remove_reference
来获得你正在寻找的值。
std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;
欲了解更多信息,请参阅这篇文章 。
为什么macros不是const
? function? 文字? types的名字?
const
东西只是不变事物的一个子集。
因为引用types就是这种types,所以要求const
限定符和其他types(特别是指针types)是一致的,但是这样做会非常繁琐。
如果C ++在默认情况下拥有不可变对象,那么对任何不想成为const
对象都要求使用mutable
关键字,那么这很容易:只是不允许程序员为引用types添加mutable
参数。
事实上,它们是不变的,没有资格。
而且,由于它们不是const
限定的,所以在引用types上的is_const
可能会更令人困惑。
我认为这是一个合理的妥协,尤其是因为不变性无论如何都是由于不存在任何语法来改变参考的单纯事实而实施的。
这是C ++中的一个怪癖/特征。 虽然我们不把引用看作是types,但实际上它们是在types系统中“坐”的。 虽然这看起来很尴尬(因为当使用引用时,引用语义会自动出现,而且引用“不受阻碍”),但是为什么引用是在types系统中build模的,而不是作为一个单独的属性types。
首先,让我们考虑一下,声明的名字的所有属性都不是必须在types系统中。 从C语言来说,我们有“存储类”和“联动”。 名称可以作为extern const int ri
引入,其中extern
表示静态存储类和链接的存在。 types只是const int
。
C ++显然包含了expression式具有types系统之外的属性的概念。 这种语言现在有一个“价值类”的概念,它试图组织一个expression式可以展现的越来越多的非types属性。
然而引用是types。 为什么?
它曾经在C ++教程中解释过,像const int &ri
这样的声明引入了ri
types为const int
,但引用语义。 引用语义不是一种types; 它只是一种属性,表明名称和存储位置之间的不寻常关系。 此外,引用不是types的事实被用来合理化为什么你不能基于引用构造types,尽pipetypes构造语法允许它。 例如,数组或指向引用的指针是不可能的: const int &ari[5]
和const int &*pri
。
但实际上引用是types的,所以decltype(ri)
检索一些不合格的引用types节点。 您必须通过types树中的此节点下降,以使用remove_reference
获取基础types。
当你使用ri
,这个引用是透明地parsing的,所以ri
“看起来和感觉像i
一样”,可以被称为“别名”。 在types系统中,虽然ri
实际上有一个“ 引用 const int
”的types。
为什么是引用types?
考虑如果引用不是types,那么这些函数将被视为具有相同的types:
void foo(int); void foo(int &);
这根本不可能是由于非常不言而喻的原因。 如果它们具有相同的types,那么意味着任一声明都适用于任何一个定义,因此每个(int)
函数都必须被怀疑参考。
同样,如果引用不是types,那么这两个类的声明将是等价的:
class foo { int m; }; class foo { int &m; };
一个翻译单位使用一个声明是正确的,另一个翻译单位在同一个程序中使用另一个声明。
事实上, 引用意味着实现上的差异,并且不可能将其与types分开,因为C ++中的types与实现的实现有关:它的“布局”就是可以这么说。 如果两个函数具有相同的types,则可以使用相同的二进制调用约定来调用它们:ABI是相同的。 如果两个结构或类具有相同的types,则它们的布局与访问所有成员的语义相同。 引用的存在改变了这些types的方面,所以将它们整合到types系统中是一个简单的devise决定。 (但是,请注意这里的一个反驳:一个结构/类成员可以是static
,这也会改变表示;但这不是types!)
因此,引用在types系统中是“二等公民”(与ISO C中的函数和数组不同)。 有些东西我们不能用引用来“做”,比如声明指向引用的指针或者它们的数组。 但这并不意味着他们不是types。 他们只是不是有意义的方式。
并不是所有这些二等限制都是必不可less的。 鉴于引用的结构,可能有引用数组! 例如
// 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 ++的人认为数组是一种从Cinheritance的低级特性,这种低级特性在很多方面都是不可修复的,而且他们不想将数组作为任何新的基础。 然而,引用数组的存在将清楚地说明引用是如何成为types的。
非常量限定types:在ISO C90中也可以find!
有些答案暗示,引用不需要const
修饰符。 这是一个红色的鲱鱼,因为声明const int &ri = i
甚至没有试图做一个const
限定的引用:它是一个const限定types(它本身不是const
)的引用。 就像const in *ri
声明一个指向const
的指针,但是指针本身不是const
。
也就是说,引用不能携带const
限定符本身。
然而,这并不奇怪。 即使在ISO C 90语言中,并非所有types都可以是const
。 也就是说,数组不能。
首先,用于声明一个常量数组的语法不存在: int a const [42]
是错误的。
然而,上面的声明试图做什么可以通过中间typedef
表示:
typedef int array_t[42]; const array_t a;
但是这看起来并不像它所做的那样。 在这个声明中,它不是a
获得const
限定的元素,而是元素! 也就是说, a[0]
是一个const int
,但是a
只是“int的数组”。 因此,这不需要诊断:
int *p = a; /* surprise! */
这样做:
a[0] = 1;
这再一次强调了引用在types系统中的某种意义上的“第二类”,比如数组。
注意类比如何更深入,因为数组也有一个“隐形转换行为”,比如引用。 没有程序员不得不使用任何显式的操作符,标识符a
自动变成一个int *
指针,就像expression式&a[0]
已被使用一样。 这类似于参考ri
,当我们用它作为主要expression式时,神奇地表示它所绑定的对象。 这只是另一个“衰退”,像“arrays指针衰减”。
就像我们不应该被“数组到指针”的衰减所迷惑,错误地认为“数组只是C和C ++中的指针”,我们同样不能认为引用只是没有自己types的别名。
当decltype(ri)
抑制引用到其引用对象的通常转换时,这sizeof a
抑制数组到指针转换的sizeof a
没有太大的区别,并且对数组types本身进行操作以计算其大小。
const X&x“表示x别名X对象,但不能通过x更改该X对象。
看std :: is_const 。