为什么有些人更喜欢使用 "T const&" 而不是 "const T&"?

74
因此,我意识到const T&T const&是相同的,都表示对const T的引用。在这两种情况下,引用也是常量(与指针不同,引用不能被重新分配)。根据我的有限经验,大多数C++程序员使用const T&,但我遇到过一些使用T const&的人。我使用const T&只是因为我是这样学的,所以T const&对我来说有点奇怪。你使用你使用的变体的原因是什么?你们中的任何人在一个编码标准强制要求使用其中之一吗? 编辑
根据答案,选择两者之间的原因之一似乎是您想要像编译器一样(从右到左)阅读还是像英语一样(从左到右)阅读。如果一个人像编译器一样阅读它,那么“T const&”就会被解释为“&(引用)const(指向常量的)T(类型为T)”。如果一个人从左到右像英语一样阅读,那么“const T&”就被解释为“一个类型为T的常量对象,以引用的形式”。我更喜欢像英语散文一样阅读它,但我当然可以理解按照编译器的方式来解释它的意义。
没有人回答组织或编码标准问题,但我强烈怀疑大多数组织不会强制要求其中之一,尽管他们可能会努力保持一致性。

34
前者被做对的人所选择,而后者被做错的人所选择。就像做对的人把花括号放在同一行,而做错的人把它放在单独的一行。 - davr
8
@davr: 我笑喷了。 - GManNickG
31
你的意思是“前者”是由做对的人做出来的,而“后者”则是由做错的人做出来的吗? - Niki Yoshiuchi
10
@davr,你的括号评论伤害了我的感情 :( 但在我看来,在单独一行上放置开括号可以使代码更易读。 - Tqn
1
可能是C++:const引用,在类型说明符之前还是之后的重复问题。 - Ciro Santilli OurBigBook.com
显示剩余5条评论
10个回答

52

我认为有些人更喜欢从右到左读取声明。 const 应用于左侧标记,除非没有标记,它会应用于右侧标记。因此,const T& 包含“except”子句,可能被认为更加复杂(实际上两者都应该很容易理解)。

比较:

const T* p;  (pointer to T that is const)
T const* p;  (pointer to const T) //<- arguable more natural to read
T* const p;  (const pointer to T)

1
@UncleBens,谢谢。所以看起来像编译器一样从右向左阅读的人更喜欢使用T const&,而那些想要像英语一样从左到右阅读的人则更喜欢使用const T&。 - Michael Aaron Safyan
实际上,英语通常是从右到左的,例如 a of b,而在其他语言中只有 b's a 可能是可能的。(事物自然阅读的方式是非常主观的,也许还取决于语言背景、思维方式的分析/综合程度等) - UncleBens
1
我理解你的意思,但是“a of b”仍然是从左到右阅读的(你说{"ay", "of", "bee"}而不是{"bee", "of", "ay"}),尽管它是从右到左评估的。 - Michael Aaron Safyan
3
@Michael,尽管写成“T const”,我仍然从左到右阅读声明。所以我说“T const”而不是“const T”。 - Johannes Schaub - litb
“……除非什么都没有[...] 因此,const T& 包含了 "except" 条款。”没错,这正是我写下 T const& 的原因,虽然让我觉得有些孤独,但我坚持自己的立场。 - v.oddou
1
“指向常量 T 的指针” - 从措辞上不清楚 T 是否为常量还是指针为常量... - einpoklum

31

当你有多个const/volatile修饰符时,这将产生影响。然后将其放在类型左边仍然有效,但会破坏整个声明的一致性。例如:

T const * const *p;

意思是p是一个指向常量指针的指针,指向常量T,并且你要始终从右到左阅读。

const T * const *p;

意思相同,但一致性丢失了,你需要记住最左边的const/volatile仅绑定到T本身而不是T *。


8
如果有人使用了 T const,然后另一个人使用了 const T,并且不小心将某些内容更改为 const T const * const p,那么这可能会产生影响。编译器会回复:error: Just how const do you want it to be? - greyfade
@greyfade,哈哈,我听说过那个错误,不过从来没有亲眼见过... 是哪个编译器呢? - Michael Aaron Safyan
@Michael Aaron Safyan:我想我曾经成功让GCC输出过它,但老实说我不记得了,也没有再次复现。 - greyfade

7
如果您觉得这个讨论很有趣,您可能会发现Dan Saks的这篇文章很有意思。它虽然并没有直接回答您的问题,但解释了他为什么更喜欢使用“const T”而不是“T const”。
VP const foo[];

为了

const VP foo[];

这是因为考虑到

typedef void *VP;

你很容易被误导认为上面的第二个例子意味着...
const void *foo[];  // Wrong, actually: void *const foo[];

但第一个示例更难被误解。


1
+1 针对文章链接,但我认为您在帖子中弄反了。 "VP const foo [];" 和 "const VP foo [];" 实际上都意味着 "void *const foo [];"。请再次检查文章。 - Maciej Hehl
感谢您的纠正。第一个例子会让您认为它实际上意味着什么(这就是为什么他更喜欢它),而让您误以为第二个例子的意思是const void *foo[]。 - Larry Engholm

5

我的推理如下:

如果您写“const T&”,听起来似乎更加流畅,但是这样做会导致歧义,“常量T引用”。我曾经看到过这种情况多次,代码的可读性会受到影响,即使是半有经验的人也可能会误解某些含义或者如何声明更复杂的类型。

我现在想不出任何例子,但是我曾经回答过关于类型声明和常量性的问题,其中问题是由于使用“const T&”而不是“T const&”的习惯造成的。我以前也是这样写的,当我成为高级开发人员、负责指导和创建项目代码标准时,我发现强制每个人都使用“T const&”对于初级开发人员来说更容易。我想一个相当琐碎的新手错误可能是,为什么这段代码可以编译?


const T* t = f();
t = 0; // assignment to const?? - no, it is not the T* that is const, just the T.

当你学会像编译器一样阅读代码后,就更容易理解任何给定的复杂类型,也能更加便捷地声明复杂类型。在这里,我所说的复杂类型是指以下内容:
T const * const &
当你知道编译器从右向左,由内而外阅读代码时,上述内容的含义就会变得十分明显。如果需要编写此类代码,你也可以轻松地完成:常量指针的引用,指向常量 T。现在请尝试编写“指向常量指针的指针的引用”的声明。如果你只使用从左到右的符号表示法,我相信你会发现这非常容易。
简而言之,尽管一开始可能感觉不自然,但如果始终使用从右到左的语法(因为它通常是必需的),而不仅仅是在必要时使用,你会发现记忆代码的含义和编写想要表达的代码会更加容易。这有点类似于为什么我不允许在声明中使用逗号。


T* ptr1 = 0, ptr2 = 0; // oops!!!

//请按照以下方式进行操作! T* ptr1 = 0; T* ptr2 = 0;

技术上来说,这些都一样,但当您尝试让许多能力不同的人一起处理同一件事情时,您会倾向于确保每个人都使用最容易理解和使用的方法。我的经验告诉我,“T const&”就是那种方法。


编译器从左到右读取。- 看起来你把方向搞反了。 - Georg Fritzsche
当我可以再次投票时,我会为你的每行一个声明和回答有人是否强制使用其中一种变体而给你+1。 - Michael Aaron Safyan
你为什么用舌头打字? ;) - Lightness Races in Orbit

4

我认为这是个人偏好。这两种变体之间没有区别。


3

由于代码主要基于英语,程序员倾向于从左到右阅读,因此const T&对我们来说自然而然地读起来很顺畅,但编译器会从右到左反向解析,所以T const&对它来说才是自然的(指一个常量T的引用)


5
人类阅读并非固定从左到右,这种偏好纯粹是文化因素造成的。相当一部分人口会以纵向阅读他们母语的文字(例如中文和日语),或从右到左阅读(例如阿拉伯语和希伯来语)。你是否曾经购买过日本漫画,并注意到它们的页面顺序是“倒过来”的(也就是书脊在右侧而不是左侧)? - Adisak
1
@Adisak:我认为他的意思是“编写代码的人倾向于”,因为大多数编写代码的人都是从左到右阅读,因为它主要基于英语。 - GManNickG
2
@Adisak,是的,你说得对,但“const”不是中文或日文单词,它是英文单词“constant”的缩写。同样,“if”,“while”,“static”等关键字也不是。C++语言与英语传统有关。如果存在一种所有关键字都是主要由汉字组成的语言,那么谈论其自然的“从右到左”的阅读方式就有意义了。但随后会有人争辩说,解释主要是文化性的,而人类倾向于从左到右阅读。也许人类足够灵活,倾向于按照语言的方向阅读,而不是相反。 - Edwin Buck
@GMan,但以色列有很多程序员,而且以色列理工学院非常出名。我认为Bruce的意思是“英语使用者”,他的观点仍然是正确的。Adisak的批评同样是正确的。 - Michael Aaron Safyan
@GMAN:是的,几乎所有的代码都是从左到右。但这又是文化条件(正如你所说,代码主要基于英语),而不是人类的倾向。假设在一个替代的过去,阿拉伯世界仍然是知识中心并发明了计算机——如果历史不同,我们阅读的可能是从右到左,并且编写代码也是从右到左。我的纠正是这不是一种普遍的“人类”倾向,而是一种文化倾向(编程也是一种文化)。 - Adisak
3
@Adisak: 我理解你的观点,承认我的措辞有误。那不是我的意图。我已经为你修正了我的回答 :) - Corey

1
我自己使用 T const& 标记,并建议每个人都这样做。原因是符合人体工程学-我发现它可以减少眼部运动。
@grego已经指出"const"靠近"&"可以避免错误。通常,当我看到"&"时,我想知道它是否为const,而来回移动我的眼睛并不是我想一直做的事情。
还有一个未提及的T const&的原因是代码对齐。让我们看看哪种声明更容易阅读:
void append(std::string& str,
            const std::vector<std::string>& elements,
            const char* delimiter);

void append(std::string& str,
            std::vector<std::string> const& elements,
            char const* delimiter);

<const T&> 把类型藏在中间,而 <T const&> 则将其暴露在左侧。<T const&> 将类型对齐到同一列中,从上到下滑动时查找特定参数类型时需要更少的左右眼运动。

出于相同的原因,我喜欢使用尾置返回类型符号 (auto f(...) -> R)。


1
我曾经是const T&的坚定支持者,因为它从逻辑上来看是从左到右的(它是一个常量T引用)。 (也许有些偏见,因为我遇到的大多数代码都是这样编写的。)
多年来,我遇到了一些边角情况(例如多个指针/引用层,多个变量声明和方法指针),这些情况有其他人已经回答的原因而使事情变得有点棘手。通常引入额外的typedefs可以在某种程度上帮助您“解决”这些情况,但是这时您必须为某些东西想出一个名称,即使它只使用一次。
对我来说,转折点是C++11中引入的auto,特别是与基于范围的for结合使用时。因此,我现在强烈倾向于T const&(或“常量T的引用”,从右到左阅读)。它不仅更符合编译器实际解析的方式,而且当您将类型替换为auto时,它总是位于左侧,我认为这样更易读。
比较:
for (const T& a : list)         for (T& a : list)
for (T const& a : list)         for (T& a : list)
for (const auto& a : list)      for (auto& a : list)
for (auto const& a : list)      for (auto& a : list)

注意右侧的列,我添加了相应的非常量版本。至少对我来说,在T之后出现const时,const与非const和auto vs. explicit似乎是最一致的情况。但这是一个风格选择,因此没有绝对正确的答案。

1

这是因为有些人发现从右到左阅读声明很有帮助。

char const*
const char*

都是指向常量字符的指针。


1
如果const&被分开得太远,就像这样:
krog::FlamBlott<std::map<HurkleKey,Spleen<int>>
speckleFlams( const krog::Floonage & floon, const std::map<krog::HackleSpoon,std::vector<krog::HeckleObservation<OBSN>>> & obsmap);

krog::FlamFinagleReport
finagleFlams( const krog::Floonage & floon, std::map<krog::HackleSpoon,std::vector<krog::HeckleObservation<OBSN>>> & obsmap)

如果不仔细看,很容易忽略第一个“obsmap”是常量引用而第二个不是。


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