对函数参数使用 const 有什么影响吗?为什么不影响函数签名?

Does using const on function parameters have any effect? Why does it not affect the function signature?

提问人:Rob 提问时间:9/23/2008 最后编辑:user16217248Rob 更新时间:10/19/2023 访问量:386290

问:

例如,假设一个简单的赋值器接受单个布尔参数:

void SetValue(const bool b) { my_val_ = b; }

这真的有什么影响吗?就我个人而言,我选择广泛使用它,包括参数,但在这种情况下,我想知道它是否有任何区别。const

我还惊讶地发现,您可以在函数声明中省略参数,但可以将其包含在函数定义中,例如:const

.h 文件

void func(int n, long l);

.cpp 文件

void func(const int n, const long l) { /* ... */ }

这是有原因的吗?这在我看来有点不寻常。

C++ 参数 函数声明 常量正确性

评论

0赞 selwyn 9/26/2008
我不同意。.h 文件也必须具有 const 定义。否则,如果将 const 参数传递给函数,编译器将生成错误,因为 .h 文件中的原型没有 const 定义。
15赞 Chris Huang-Leaver 4/20/2009
我同意。:-)(有问题,不是最后一条评论!如果不应该在函数的主体中更改值,这可以帮助停止愚蠢的 == 或 = 错误,你永远不应该将 const 放在两者中,(如果它是按值传递的,否则你必须)这还不够严重,不足以引起争论!
27赞 jalf 5/5/2009
@selwyn:但是,即使你将 const int 传递给函数,它也会被复制(因为它不是引用),所以 const-ness 无关紧要。
1赞 Partial 11/12/2009
同样的争论也发生在这个问题上:stackoverflow.com/questions/1554750/......
7赞 9/20/2011
我意识到这篇文章已经有几年了,但作为一名新程序员,我想知道这个问题,我偶然发现了这个对话。在我看来,如果一个函数不应该改变一个值,无论是引用还是值/对象的副本,它都应该是常量。它更安全,可以自我记录,并且更易于调试。即使对于最简单的函数,它只有一个语句,我仍然使用 const。

答:

242赞 Greg Rogers 9/23/2008 #1

原因是该参数仅在函数中本地应用,因为它正在处理数据的副本。这意味着函数签名无论如何都是相同的。不过,经常这样做可能是不好的风格。const

我个人倾向于不使用引用和指针参数。对于复制的对象,这并不重要,尽管它可能更安全,因为它在函数中发出意图信号。这真的是一个判断电话。我确实倾向于在循环某些东西时使用 though,并且我不打算修改它,所以我猜每个人都有自己的,只要严格维护引用类型的正确性。constconst_iteratorconst

评论

77赞 Michał Górny 1/9/2010
我不能同意“糟糕的风格”部分。从函数原型中删除的优点是,如果您决定稍后从实现部分删除,则无需更改头文件。constconst
11赞 Deduplicator 9/10/2014
“我个人倾向于不使用 const,除了引用和指针参数。”也许你应该澄清一下,“我倾向于不在函数声明中使用多余的限定符,而是在它产生有用差异的地方使用。const
8赞 Ultimater 8/2/2016
我不同意这个答案。我向另一个方向倾斜,并尽可能标记参数;它更具表现力。当我阅读别人的代码时,我会使用这样的小指标来判断他们在编写代码时所付出的程度,以及诸如幻数、注释和正确的指针用法等。const
5赞 dom_beau 3/27/2017
int getDouble(int a){ ++a; return 2*a; }试试这个。当然,这与此无关,但它可以在多个程序员在很长一段时间内编写的长函数中找到。我强烈建议编写在查找时会产生编译错误的编写。++aint getDouble( const int a ){ //... }++a;
13赞 CharonX 10/1/2018
这完全取决于谁需要哪些信息。您可以按值提供参数,以便调用方不需要知道您(内部)使用它执行的操作。所以写在你的标题中。在你的实现中,你确实关心你可以承诺不改变,所以在这里是有道理的。(旁注:调用者和实现都关心函数不会改变其对象这一事实,因此在其声明的末尾为 const)class Foo { int multiply(int a, int b) const; }abint Foo::multiply(const int a, const int b) const { }Foo
10赞 Asaf R 9/23/2008 #2

我对函数参数使用 const,这些参数是引用(或指针),这些参数只是 [in] 数据,不会被函数修改。这意味着,当使用引用的目的是避免复制数据并且不允许更改传递的参数时。

在示例中,将 const 放在布尔值 b 参数上只会对实现施加约束,并且不会对类的接口产生影响(尽管通常建议不要更改参数)。

函数签名

void foo(int a);

void foo(const int a);

是一样的,这解释了你的 .c 和 .h

阿萨夫

2赞 Luke Halliwell 9/23/2008 #3

在你提到的情况下,它不会影响你的 API 的调用者,这就是为什么它不经常这样做(并且在标头中不是必需的)。它只影响函数的实现。

这并不是一件特别坏的事情,但好处并不是那么大,因为它不会影响你的 API,而且它增加了类型,所以通常不会这样做。

0赞 Khoth 9/23/2008 #4

我不会把 const 放在这样的参数上——每个人都已经知道布尔值(而不是布尔值&)是常量,所以添加它会让人们认为“等等,什么?”,甚至你正在通过引用传递参数。

评论

4赞 gbjbaanb 9/23/2008
有时,您希望通过引用传递对象(出于性能原因),但不更改它,因此 const 是强制性的。保留所有这些参数 - 甚至是布尔值 - const 将是很好的做法,使你的代码更易于阅读。
0赞 gbjbaanb 9/23/2008 #5

要记住的一点是,从一开始就让事情变得 const 比以后再尝试把它们放进去要容易得多。

当您希望某些内容保持不变时,请使用 const - 它是一个附加提示,用于描述您的函数的作用和预期内容。我见过许多可以与其中一些一起使用的 C API,尤其是那些接受 c 字符串的 API !

我更倾向于在 cpp 文件中省略 const 关键字而不是标头,但是由于我倾向于剪切+粘贴它们,因此它们将保留在两个位置。我不知道为什么编译器允许这样做,我想这是编译器的事情。最佳做法肯定是将 const 关键字放在两个文件中。

评论

0赞 Don Hatch 10/14/2016
我完全不明白。为什么倾向于在 cpp 文件(函数定义)中省略它?这就是它实际上意味着什么并且可以捕获错误的地方。为什么你认为将 const 放在这两个地方是最好的做法?在头文件(函数声明)中,它没有任何意义,并且使 API 混乱。也许让 decl 和 defn 看起来完全相同有一些小价值,但在我看来,与混乱的 API 问题相比,这是一个非常小的好处。
0赞 gbjbaanb 10/14/2016
@DonHatch 8 年后,哇。无论如何,正如 OP 所说,“我也很惊讶地发现,您可以在函数声明中的参数中省略 const,但可以将其包含在函数定义中”。
5赞 Nemanja Trifunovic 9/23/2008 #6

啊,一个艰难的。一方面,声明是契约,按值传递常量参数确实没有意义。另一方面,如果你看一下函数实现,如果你声明一个参数常量,你就会给编译器更多的优化机会。

-1赞 AShelly 9/23/2008 #7

示例中的所有常量都没有目的。默认情况下,C++ 是按值传递的,因此该函数会获取这些 int 和 boolean 的副本。即使函数修改了它们,调用方的副本也不会受到影响。

所以我会避免额外的常量,因为

  • 它们是多余的
  • 他们杂乱无章 正文
  • 他们阻止我 更改传入的值 它可能有用或有效的情况。

评论

1赞 mercury0114 2/15/2023
They prevent me from changing the passed in value in cases where it might be useful or efficient.- 但这正是您可能希望在函数定义中将参数标记为 const 的原因(而不是声明!):以指示参数(现在是局部变量)不会更改。非常量参数可能指示您的实现计划更改局部变量。
-2赞 MarkR 9/23/2008 #8

实际上没有理由将值参数设置为“const”,因为该函数无论如何都只能修改变量的副本。

使用“const”的原因是,如果你通过引用传递更大的东西(例如,一个有很多成员的结构体),在这种情况下,它确保函数不能修改它;或者更确切地说,如果您尝试以传统方式修改它,编译器会抱怨。它可以防止它被意外修改。

2赞 Dror Helper 9/23/2008 #9

我尽可能使用 const。参数的常量意味着它们不应更改其值。这在通过引用传递时尤其有价值。const for function 声明函数不应更改类成员。

2赞 Aurélien Gâteau 9/23/2008 #10

我不将 const 用于值传递的参数。调用方不关心你是否修改参数,这是一个实现细节。

真正重要的是,如果方法不修改其实例,则将其标记为 const。边走边做,否则你最终可能会得到很多const_cast<>或者你可能会发现标记一个方法const需要更改大量代码,因为它调用了其他应该被标记为const的方法。

如果我不需要修改它们,我也倾向于标记本地变量 const。我相信它使代码更容易识别“移动部件”,从而使代码更容易理解。

-1赞 Yogish Baliga 9/23/2008 #11

Const 参数仅在参数通过引用(即引用或指针)传递时才有用。当编译器看到 const 参数时,它会确保参数中使用的变量不会在函数体中被修改。为什么有人想将 by-value 参数设置为常量?:-)

评论

0赞 underscore_d 9/24/2018
原因有很多。制作一个 by-value 参数清楚地表明:“我不需要修改它,所以我声明它。如果我稍后尝试修改它,请给我一个编译时错误,以便我可以修复我的错误或取消标记为 。因此,这既关系到代码卫生,也关系到安全。对于添加到实现文件所需的所有内容,它应该是人们纯粹反射性的事情,IMO。constconst
1赞 Attila 9/23/2008 #12

如果参数是按值传递的(并且不是引用),则通常参数是否声明为 const 没有太大区别(除非它包含引用成员 - 对于内置类型来说不是问题)。如果参数是引用或指针,通常最好保护引用/指向的内存,而不是指针本身(我认为你不能使引用本身成为常量,这并不重要,因为你不能改变引用)。 保护一切似乎是个好主意。如果参数只是 POD(包括内置类型),并且它们不会进一步更改(例如,在您的示例中为 bool 参数),则可以省略它而不必担心出错。

我不知道 .h/.cpp 文件声明的差异,但它确实有一定意义。在机器代码级别,没有什么是“const”的,所以如果你将一个函数(在 .h 中)声明为非常量,则代码与将其声明为 const 是一样的(除了优化)。但是,它可以帮助您登记编译器,以确保您不会更改函数 (.ccp) 实现中的变量值。当您从允许更改的接口继承时,它可能会派上用场,但您不需要更改参数即可实现所需的功能。

4赞 jdmichal 9/23/2008 #13

我倾向于尽可能使用 const。(或目标语言的其他适当关键字。我这样做纯粹是因为它允许编译器进行额外的优化,否则它将无法进行。由于我不知道这些优化可能是什么,所以我总是这样做,即使看起来很傻。

据我所知,编译器很可能会看到一个 const value 参数,然后说,“嘿,这个函数无论如何都不会修改它,所以我可以通过引用传递并保存一些时钟周期。我不认为它会做这样的事情,因为它改变了函数签名,但它说明了这一点。也许它做了一些不同的堆栈操作或其他东西......关键是,我不知道,但我知道试图比编译器更聪明只会导致我感到羞耻。

C++有一些额外的包袱,具有常量正确性的想法,因此它变得更加重要。

评论

0赞 underscore_d 9/24/2018
虽然它在某些情况下可能会有所帮助,但我怀疑促进优化的可能性被夸大了。相反,这是一个在实现中陈述意图并在以后捕获 thinkoes 的问题(意外地增加了错误的局部变量,因为它不是)。同时,我还要补充一点,非常欢迎编译器更改函数签名,从某种意义上说,函数可以内联,一旦内联,它们的整个工作方式就可以改变;添加或删除引用、制作“变量”文字等都在 as-if 规则之内constconst
95赞 Ben Straub 9/23/2008 #14

以下两行在功能上是等效的:

int foo (int a);
int foo (const int a);

显然,如果以第二种方式定义,您将无法在正文中进行修改,但与外部没有区别。afoo

真正派上用场的是引用或指针参数:const

int foo (const BigStruct &a);
int foo (const BigStruct *a);

这说明,foo 可以接受一个大参数,也许是一个千兆字节大小的数据结构,而无需复制它。此外,它还对调用方说,“Foo 不会*更改该参数的内容。传递 const 引用还允许编译器做出某些性能决策。

*:除非它抛弃了恒常性,但那是另一篇帖子。

评论

4赞 tml 5/5/2016
这不是这个问题的意义所在;当然,对于引用或指向的参数,最好使用 const(如果未修改引用或指向的值)。请注意,在指针示例中,参数不是 const;这是参数所指向的东西。
0赞 jheriko 10/5/2016
> 传递 const 引用还允许编译器做出某些性能决策。经典谬误 - 编译器必须自己确定常量,const 关键字对此无济于事,这要归功于指针别名和const_cast
5赞 Dan Hewett 9/23/2008 #15

当参数按值传递时,const 毫无意义,因为您不会修改调用方的对象。

通过引用传递时,应首选 const,除非函数的目的是修改传递的值。

最后,一个不修改当前对象 (this) 的函数可以并且可能应该声明为 const。示例如下:

int SomeClass::GetValue() const {return m_internalValue;}

这是不修改应用此调用的对象的承诺。换句话说,您可以调用:

const SomeClass* pSomeClass;
pSomeClass->GetValue();

如果函数不是 const,则会导致编译器警告。

530赞 rlerallut 9/23/2008 #16

当参数按值传递时,const 是没有意义的,因为你会 不得修改调用方的对象。

错。

这是关于自我记录你的代码和你的假设。

如果你的代码有很多人在处理它,并且你的函数是非平凡的,那么你应该标记任何你能标记的东西。在编写工业级代码时,你应该始终假设你的同事是精神病患者,他们试图以任何可能的方式让你(特别是因为将来通常是你自己)。const

此外,正如前面提到的,它可能有助于编译器进行一些优化(尽管这是一个很长的镜头)。

评论

57赞 Len Holgate 9/23/2008
完全同意。这一切都是关于与人沟通,并将变量可以做的事情限制在应该做的事情上。
36赞 tonylo 9/23/2008
我已经投了反对票。我认为当你将 const 应用于简单的传递值参数时,你稀释了你试图用 const 指示的内容。
37赞 Richard Corden 9/23/2008
我已经投了这个票。声明参数“const”会向参数添加语义信息。它们突出显示了代码原始作者的意图,这将有助于随着时间的推移维护代码。
18赞 rlerallut 9/23/2008
@tonylo:你误会了。这是关于在代码块(恰好是一个函数)中将局部变量标记为 const。我会对任何局部变量做同样的事情。它与拥有常量正确的 API 是正交的,这确实也很重要。
71赞 Adrian 10/1/2008
它可以捕获函数内部的错误——如果你知道一个参数不应该被改变,那么声明它 const 意味着编译器会告诉你你是否不小心修改了它。
182赞 Constantin 9/23/2008 #17

有时(太频繁了!我必须解开别人的 C++ 代码。我们都知道,别人的C++代码几乎从定义上讲是一团糟:)因此,我破译本地数据流的第一件事是将 const 放在每个变量定义中,直到编译器开始吠叫。这也意味着 const 限定值参数,因为它们只是调用者初始化的花哨局部变量。

啊,我希望变量默认是常量,非常量变量需要可变:)

评论

9赞 ysap 1/23/2017
“我希望变量默认是常量”——矛盾??8-)说真的,“纠缠”一切如何帮助你解开代码?如果原作者更改了一个假定的常数参数,你怎么知道 var 应该是一个常数?此外,绝大多数(非参数)变量都是......变量。所以编译器应该在你开始这个过程后很快中断,不是吗?
17赞 Constantin 1/30/2017
@ysap, 1.尽可能多地标记 const 可以让我看到哪些部分在移动,哪些没有。根据我的经验,许多当地人事实上是常客,而不是相反。2. “Const 变量”/“Immutable variable”听起来可能很矛盾,但这是函数式语言以及一些非函数式语言的标准做法;参见 Rust 示例:doc.rust-lang.org/book/variable-bindings.html
2赞 anatolyg 6/21/2017
现在在某些情况下,在 c++ 中也是标准的;例如,lambda 是一个错误;看这里[x](){return ++x;}
23赞 johnthagen 7/31/2017
在 Rust :)中,变量默认为 “”const
5赞 Joshua Jurgensmeier 3/25/2022
当然,“const”antin 会这么说!对不起,我很惊讶没有其他人说过......
24赞 2 revsAvdi #18

当我以编写 C++ 为生时,我尽我所能。使用 const 是帮助编译器帮助你的好方法。例如,对方法返回值进行设置可以使您免于拼写错误,例如:

foo() = 42

当你的意思是:

foo() == 42

如果 foo() 被定义为返回非常量引用:

int& foo() { /* ... */ }

编译器很乐意让您为函数调用返回的匿名临时值赋值。使其常量:

const int& foo() { /* ... */ }

消除了这种可能性。

评论

7赞 gavrie 2/9/2010
这是用什么编译器工作的?GCC 在尝试编译时出现错误:错误:需要 lvalue 作为赋值的左操作数foo() = 42
0赞 Josh 9/19/2011
这是不正确的。foo() = 42 与 2 = 3 相同,即编译器错误。返回 const 是完全没有意义的。它不对内置类型执行任何操作。
2赞 Mephane 9/23/2011
我遇到过 const 的这种用法,我可以告诉你,最终它产生的麻烦多于好处。提示:与 的类型不同,如果您使用函数指针、信号/插槽系统或 boost::bind 之类的东西,这会给您带来很大的麻烦。const int foo()int foo()
2赞 Avdi 11/29/2011
我更正了代码以包含引用返回值。
0赞 Zantier 5/8/2014
由于返回值优化,实际上不是和 一样吗?const int& foo()int foo()
5赞 Lloyd 9/23/2008 #19

将值参数标记为“const”绝对是一件主观的事情。

但是,我实际上更喜欢将值参数标记为 const,就像在您的示例中一样。

void func(const int n, const long l) { /* ... */ }

对我来说,值清楚地表明函数参数值永远不会被函数更改。它们在开始时和结束时具有相同的值。对我来说,这是保持非常实用的编程风格的一部分。

对于一个短函数来说,将“const”放在那儿可以说是浪费时间/空间,因为通常很明显参数不会被函数修改。

但是,对于较大的函数,它是一种实现文档形式,由编译器强制执行。

我可以肯定,如果我用“n”和“l”进行一些计算,我可以重构/移动该计算,而不必担心得到不同的结果,因为我错过了一个或两个都发生了变化的地方。

由于它是实现细节,因此无需在标头中声明值参数 const,就像不需要声明与实现使用的名称相同的函数参数一样。

0赞 siddharth 9/23/2008 #20

由于参数是按值传递的,因此从调用函数的角度来看,是否指定 const 没有任何区别。基本上,将按值传递参数声明为 const 没有任何意义。

38赞 QBziZ 9/23/2008 #21

const 应该是 C++ 中的默认值。 喜欢这个:

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable

评论

27赞 3/16/2010
与 C 的兼容性太重要了,至少对于设计 C++ 的人来说,甚至不会考虑这一点。
4赞 Dan 9/19/2012
有趣的是,我从来没有想过这一点。
7赞 hkBattousai 7/21/2015
同样,应该是 C++ 中的默认值。像这样: 和 .unsignedint i = 5; // i is unsignedsigned int i = 5; // i is signed
0赞 Yongwei Wu 10/28/2022
@hkBattousai 为什么?const-by-default 有一个基本原理,但我在 unsigned-by-default 中没有看到它。事实上,Bjarne 认为 size() 返回无符号类型是一个设计错误。在 span 的初始设计中,人们希望对 size() 进行签名,但为时已晚——与容器的 size() 不兼容并不是大多数 C++ 标准提交者成员想要的。
9赞 Ola Ost 11/14/2008 #22

我说const你的值参数。

考虑这个有缺陷的函数:

bool isZero(int number)
{
  if (number = 0)  // whoops, should be number == 0
    return true;
  else
    return false;
}

如果 number 参数是 const,编译器将停止并警告我们该错误。

评论

3赞 Johannes Schaub - litb 11/14/2008
另一种方法是使用 if(0 == number) ...还。。。;
5赞 MPelletier 1/18/2012
@ChrisHuang-Leaver 这并不可怕,如果你像尤达那样说话:stackoverflow.com/a/2430307/210916
1赞 Jetski S-type 10/25/2018
GCC/Clang -Wall 给了你 -Wparentheses,它要求你让它 “if ((number = 0))”,如果这确实是你打算做的。这可以很好地替代尤达。
15赞 Void - Othman 5/5/2009 #23

在comp.lang.c++.moderated上的旧“本周大师”文章中,有关于这个话题的很好的讨论。

相应的 GOTW 文章可在 Herb Sutter 的网站上找到

评论

1赞 Adisak 6/14/2012
Herb Sutter是一个非常聪明的人:-)绝对值得一读,我同意他的所有观点。
2赞 QBziZ 7/3/2012
好吧,好文章,但我不同意他的论点。我也让它们成为常量,因为它们就像变量一样,我不希望任何人对我的论点进行任何更改。
2赞 sdcvvc 5/6/2009 #24

关于编译器优化:http://www.gotw.ca/gotw/081.htm

96赞 Adisak 6/14/2012 #25

从 API 的角度来看,额外的多余常量是不好的:

在代码中为按值传递的内部类型参数添加额外的多余常量会使 API 混乱,同时不会对调用者或 API 用户做出有意义的承诺(这只会阻碍实现)。

当不需要时,API 中过多的“const”就像“狼来了”,最终人们会开始忽略“const”,因为它无处不在,而且大多数时候没有任何意义。

API 中额外常量的“reductio ad absurdum”参数对前两点有好处,如果更多的 const 参数是好的,那么每个可以有 const 的参数都应该有一个 const。事实上,如果它真的那么好,你会希望 const 成为参数的默认值,并且只有在你想更改参数时才使用像 “mutable” 这样的关键字。

因此,让我们尝试尽可能地放入常量:

void mungerum(char * buffer, const char * mask, int count);

void mungerum(char * const buffer, const char * const mask, const int count);

考虑上面的代码行。不仅声明更杂乱、更长、更难阅读,而且 API 用户可以安全地忽略四个“const”关键字中的三个。然而,“const”的额外使用使第二行具有潜在的危险性!

为什么?

对第一个参数的快速误读可能会使您认为它不会修改传入的数据缓冲区中的内存 - 但是,事实并非如此!多余的“const”在快速扫描或误读时会导致对 API 的危险和不正确的假设char * const buffer


从代码实现的角度来看,多余的常量也是不好的:

#if FLEXIBLE_IMPLEMENTATION
       #define SUPERFLUOUS_CONST
#else
       #define SUPERFLUOUS_CONST             const
#endif

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count);

如果FLEXIBLE_IMPLEMENTATION不成立,则 API “承诺”不会以以下第一种方式实现该功能。

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       // Will break if !FLEXIBLE_IMPLEMENTATION
       while(count--)
       {
              *dest++=*source++;
       }
}

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       for(int i=0;i<count;i++)
       {
              dest[i]=source[i];
       }
}

这是一个非常愚蠢的承诺。你为什么要做出一个承诺,对你的来电者没有任何好处,只会限制你的实施?

不过,这两者都是同一函数的完全有效实现,因此您所做的只是不必要地将一只手绑在背后。

此外,这是一个非常肤浅的承诺,很容易(并且在法律上被规避)。

inline void bytecopyWrapped(char * dest,
   const char *source, int count)
{
       while(count--)
       {
              *dest++=*source++;
       }
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source,SUPERFLUOUS_CONST int count)
{
    bytecopyWrapped(dest, source, count);
}

看,无论如何,我都以这种方式实现了它,即使我答应不这样做——只是使用包装器函数。这就像坏人承诺不会在电影中杀死某人并命令他的追随者杀死他们一样。

那些多余的常量只不过是电影坏人的承诺。


但撒谎的能力变得更糟:

我被启发了,你可以通过使用虚假的const在header(声明)和代码(定义)中不匹配const。const-happy 的拥护者声称这是一件好事,因为它让你只把 const 放在定义中。

// Example of const only in definition, not declaration
struct foo { void test(int *pi); };
void foo::test(int * const pi) { }

然而,反之亦然......您只能在声明中放置虚假的常量,而在定义中忽略它。这只会让 API 中多余的常量变得更加可怕和可怕的谎言 - 请参阅以下示例:

struct foo
{
    void test(int * const pi);
};

void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
    pi++;  // I promised in my definition I wouldn't modify this
}

实际上,所有多余的 const 所做的就是在实现者想要更改变量或通过非常量引用传递变量时,通过强制他使用另一个本地副本或包装函数来降低实现者的代码的可读性。

请看这个例子。哪个更具可读性?很明显,第二个函数中出现额外变量的唯一原因是因为某些 API 设计者抛入了一个多余的常量?

struct llist
{
    llist * next;
};

void walkllist(llist *plist)
{
    llist *pnext;
    while(plist)
    {
        pnext=plist->next;
        walk(plist);
        plist=pnext;    // This line wouldn't compile if plist was const
    }
}

void walkllist(llist * SUPERFLUOUS_CONST plist)
{
    llist * pnotconst=plist;
    llist *pnext;
    while(pnotconst)
    {
        pnext=pnotconst->next;
        walk(pnotconst);
        pnotconst=pnext;
    }
}

希望我们在这里学到了一些东西。多余的常量是 API 杂乱无章的碍眼,是烦人的唠叨,是肤浅而无意义的承诺,是不必要的障碍,偶尔会导致非常危险的错误。

评论

11赞 Adisak 2/23/2013
为什么投反对票?如果你对反对票发表简短的评论,会更有帮助。
9赞 touko 3/5/2013
使用 const 参数的全部意义在于使标记的行失败 (plist = pnext)。保持函数参数不可变是一种合理的安全措施。我确实同意你的观点,即它们在函数声明中很糟糕(因为它们是多余的),但它们可以在实现块中实现它们的目的。
27赞 jw013 8/16/2013
@Adisak 我看不出你的回答有什么问题,但从你的评论来看,你似乎错过了一个重要的点。函数定义/实现不是 API 的一部分,它只是函数声明。正如您所说,使用 const 参数声明函数是没有意义的,并且会增加混乱。但是,API 的用户可能永远不需要看到其实现。同时,为了清楚起见,实现者可能决定对函数定义中的某些参数进行限定,这完全没问题。
19赞 Oktalist 2/9/2014
@jw013是正确的,并且是完全相同的功能,而不是重载。ideone.com/npN4W4 ideone.com/tZav9R 这里的 const 只是函数体的一个实现细节,对重载解析没有影响。为了更安全、更整洁的 API,将 const 排除在声明之外,但如果您不打算修改复制的值,请将 const 放入定义中。void foo(int)void foo(const int)
4赞 RamblingMad 7/15/2014
@Adisak我知道这已经过时了,但我相信公共 API 的正确用法是相反的。这样一来,在内部工作的开发人员就不会犯错误,例如他们不应该犯错误。pi++
6赞 user541686 7/24/2012 #26

如果您使用 or 运算符,则必须这样做。->*.*

它阻止你写类似的东西

void foo(Bar *p) { if (++p->*member > 0) { ... } }

我现在几乎做到了,而且可能没有达到你的意图。

我想说的是

void foo(Bar *p) { if (++(p->*member) > 0) { ... } }

如果我在 和 之间放一个,编译器会告诉我。constBar *p

评论

6赞 mk12 8/26/2012
当我要将这么多运算符混合在一起时,我会立即检查有关运算符优先级的参考(如果我还不知道 100%),所以 IMO 这不是问题。
1赞 Gabriel Staples 2/27/2021
我会将这 1 个困难的行分解成大约 5 个或更多清晰易读的行,每个行都有一个描述性变量名称,使整个操作自我记录。所以,对我来说,这不是问题。在我看来,当可读性受到影响并且错误蔓延时,将代码压缩到一行中并不是一个好主意。
4赞 HarshaXsoad 4/4/2015 #27

也许这不是一个有效的论点。但是,如果我们在函数编译器中增加 const 变量的值,则会给我们一个错误: “错误:只读参数的增量”。因此,这意味着我们可以使用 const 关键字来防止意外修改函数中的变量(我们不应该/只读)。因此,如果我们在编译时不小心这样做了,编译器会让我们知道这一点。如果您不是唯一从事此项目的人,这一点尤其重要。

-1赞 PavDub 4/8/2015 #28

我知道这个问题“有点”过时了,但当我遇到它时,其他人将来可能也会这样做......我仍然怀疑这个可怜的家伙会在这里列出来阅读我的评论:)

在我看来,我们仍然过于局限于C风格的思维方式。在 OOP 范式中,我们玩的是对象,而不是类型。const 对象在概念上可能与非 const 对象不同,特别是在逻辑 const 的意义上(与按位 const 相反)。因此,即使函数参数的 const 正确性(也许)在 POD 的情况下过于谨慎,但在对象的情况下却不是这样。如果一个函数与一个 const 对象一起工作,它应该这样说。请考虑以下代码片段

#include <iostream>

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SharedBuffer {
private:

  int fakeData;

  int const & Get_(int i) const
  {

    std::cout << "Accessing buffer element" << std::endl;
    return fakeData;

  }

public:

  int & operator[](int i)
  {

    Unique();
    return const_cast<int &>(Get_(i));

  }

  int const & operator[](int i) const
  {

    return Get_(i);

  }

  void Unique()
  {

    std::cout << "Making buffer unique (expensive operation)" << std::endl;

  }

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NonConstF(SharedBuffer x)
{

  x[0] = 1;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ConstF(const SharedBuffer x)
{

  int q = x[0];

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main()
{

  SharedBuffer x;

  NonConstF(x);

  std::cout << std::endl;

  ConstF(x);

  return 0;

}

PS.:你可能会争辩说(const)引用在这里更合适,并且给你相同的行为。嗯,对。只是给出了与我在其他地方看到的不同的画面......

-1赞 Paul Stearns 5/28/2017 #29

作为一个 VB.NET 程序员,需要使用具有 50+ 公开函数的 C++ 程序,以及偶尔使用 const 限定符的 .h 文件,很难知道何时使用 ByRef 或 ByVal 访问变量。

当然,程序通过在你犯错的行上生成异常错误来告诉你,但随后你需要猜测 2-10 个参数中的哪一个是错误的。

因此,现在我有一个令人反感的任务,即试图说服开发人员,他们应该以一种允许轻松创建所有 VB.NET 函数定义的自动化方法的方式真正定义他们的变量(在 .h 文件中)。然后他们会得意地说,“阅读......文档。

我编写了一个 awk 脚本来解析 .h 文件,并创建所有 Declare Function 命令,但是没有指示哪些变量是 R/O 与 R/W,它只完成了一半的工作。

编辑:

在另一位用户的鼓励下,我添加了以下内容;

下面是一个 (IMO) 格式不佳的 .h 条目示例;

typedef int (EE_STDCALL *Do_SomethingPtr)( int smfID, const char* cursor_name, const char* sql );

我的脚本生成的 VB;

    Declare Function Do_Something Lib "SomeOther.DLL" (ByRef smfID As Integer, ByVal cursor_name As String, ByVal sql As String) As Integer

请注意第一个参数上缺少的“const”。没有它,程序(或其他开发人员)不知道第一个参数应该被传递“ByVal”。通过添加“const”,它使 .h 文件自记录,以便使用其他语言的开发人员可以轻松编写工作代码。

评论

0赞 Paul Stearns 5/29/2017
@anatolyg,你是对的,这是一个咆哮,但它确实确定了在语言中没有正确使用概念的问题,这些概念可能不是必需的,但提供了可用性和自我文档。如果您编写的代码需要其他开发人员使用,请务必使其可用。如果有人觉得它有用,我很乐意发布 AWK 脚本,但它只有在 .h 文件格式正确的情况下才有效。
0赞 anatolyg 5/29/2017
为了说明你的观点,你最好发布一个坏例子和一个好例子。还有翻译后的文件,并显示脚本有问题的地方,以及如果您在正确的位置使用,问题如何消失。*.hconst
0赞 Paul Stearns 5/29/2017
@anatolyg,这样更好吗?
0赞 anatolyg 5/29/2017
至少现在我明白你想说什么了!但我不同意:两者在 C++ 中的意思是“按值传递”,而意味着“通过引用传递”。int smfIDconst int smfIDint& smfID
0赞 Paul Stearns 5/29/2017
@anatolyg .h 文件中没有“&”,这些参数似乎可以互换用于 ByRef/ByVal。我发现的唯一方法是搜索 .CHM 文件文档,或者等到我的程序因“System.AccessViolationException”错误而爆炸,然后尝试找出它是哪个参数。我当前使用的特定 dll 有 ~100 个公共方法,我想自动创建“声明函数”。
2赞 YvesgereY 7/26/2017 #30

总结一下:

  • “通常情况下,const pass-by-value 充其量是无用的,而且具有误导性。”从 GOTW006
  • 但是,您可以像使用变量一样将它们添加到.cpp中。
  • 请注意,标准库不使用 const。对标准库来说足够好的东西对我有好处。std::vector::at(size_type pos)

评论

2赞 anatolyg 7/27/2017
“对标准库来说足够好的东西对我有好处”并不总是正确的。例如,标准库一直使用丑陋的变量名称 - 你不想要这个(实际上你不允许使用它们)。_Tmp
1赞 Fernando Pelliccioni 9/21/2017
@anatolyg这是一个实现细节
2赞 anatolyg 9/23/2017
好的,参数列表中的变量名和常量限定类型都是实现细节。我想说的是,标准库的实现有时并不好。有时,你可以(也应该)做得更好。标准库的代码是什么时候写的 - 10 年前?5 年前(它的一些最新部分)?我们今天可以写出更好的代码。
19赞 Gabriel Staples 3/24/2020 #31

1. 根据我的评估的最佳答案:

根据我的评估,@Adisak的答案是这里最好的答案。请注意,这个答案在一定程度上是最好的,因为它也是最有真实代码示例支持的答案,此外还使用了合理且经过深思熟虑的逻辑。

2. 我自己的话(同意最佳答案):

  1. 对于按值传递,添加 .它所做的只是:const
    1. 限制实现者每次想要更改源代码中的输入参数时都必须制作一个副本(无论如何,这种更改都不会产生副作用,因为传入的内容已经是副本,因为它是按值传递的)。通常,更改按值传递的输入参数用于实现函数,因此到处添加可能会阻碍这一点。const
    2. 并且添加不必要的代码会使代码与 S 无处不在,从而将注意力从拥有安全代码真正必要的 S 上移开。constconstconst
  2. 然而,在处理指针或引用时,在需要时至关重要,并且必须使用,因为它可以防止函数外部的持续更改产生不必要的副作用,因此当参数仅是输入而不是输出时,必须使用每个指针引用对通过引用或指针传递的参数使用还有一个额外的好处,即可以非常清楚地了解哪些参数是指针或引用。坚持说“小心!任何旁边的参数都是引用或指针!constconstconstconst
  3. 我上面描述的通常是我工作过的专业软件组织达成的共识,并被认为是最佳实践。有时,这条规则甚至很严格:“永远不要对按值传递的参数使用 const,但如果它们只是输入,则始终在通过引用或指针传递的参数上使用 const。

3.谷歌的话(同意我和最好的答案):

(摘自“Google C++ 风格指南")

对于按值传递的函数参数,const 对调用方没有影响,因此不建议在函数声明中使用。请参阅 TotW #109

既不鼓励也不鼓励对局部变量使用 const。

资料来源:Google C++风格指南的“常量的使用”部分:https://google.github.io/styleguide/cppguide.html#Use_of_const。这实际上是一个非常有价值的部分,因此请阅读整个部分。

请注意,“TotW #109”代表“本周提示 #109:函数声明中的有意义的常量,也是一本有用的读物。它提供了更多的信息,而不是关于做什么的规范性,并且基于上下文,在上面引用的 Google C++ 风格指南规则之前,但由于它提供的清晰度,上面引用的规则被添加到 Google C++ 风格指南中。constconst

另请注意,即使我在这里引用Google C++风格指南来捍卫我的立场,这并不意味着我总是遵循指南或总是建议遵循指南。他们推荐的一些东西很奇怪,比如他们针对“常量名称”的 kDaysInAWeek 样式命名约定然而,当世界上最成功和最有影响力的技术和软件公司之一使用与我和其他类似@Adisak相同的理由来支持我们在这个问题上的观点时,仍然有用和相关。

4. Clang 的 linter, , 对此有一些选择:clang-tidy

一个。还值得注意的是,Clang 的 linter 有一个选项,此处描述,用于支持在代码库中强制执行使用 const 作为按值传递函数参数clang-tidyreadability-avoid-const-params-in-decls

检查函数声明是否具有顶级 const 参数。

声明中的 const 值不会影响函数的签名,因此不应将它们放在那里。

例子:

void f(const string);   // Bad: const is top level.
void f(const string&);  // Good: const is not top level.

为了完整和清晰起见,我自己添加了另外两个示例:

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

B.它还具有此选项: - https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.htmlreadability-const-return-type

5. 我务实地对待如何就此事撰写风格指南:

我只需将其复制并粘贴到我的风格指南中:

[复制/粘贴开始]

  1. 当函数参数的内容(它们指向的内容)不打算更改时,请始终使用通过引用或指针传递的函数参数。这样,当通过引用或指针传递的变量 IS 被更改时,它就变得很明显了,因为它将缺少 .在此用例中,可防止函数外部的意外副作用。constconstconst
  2. 不建议在按值传递的函数参数上使用,因为对调用方没有影响:即使在函数中更改了变量,也不会在函数之外产生副作用。有关其他理由和见解,请参阅以下资源:constconst
    1. “Google C++ 风格指南”“常量的使用”部分
    2. “本周提示 #109:函数声明中有意义的常量
    3. Adisak 的 Stack Overflow 关于“使用'const'作为函数参数”的答案
  3. "切勿非定义的声明中对函数参数使用顶级 [ie: on parameters by value] (并注意不要复制/粘贴无意义的 )。它毫无意义,被编译器忽略了,它是视觉噪音,它可能会误导读者“(https://abseil.io/tips/109,强调后加)。constconstconst
  4. 唯一对编译有影响的限定符是放置在函数定义中的限定符,而不是函数正向声明中的限定符,例如头文件中的函数(方法)声明中的限定符。const
  5. 切勿对函数返回的值使用顶级 [ie: on variables passing by value]。constconst
  6. 使用函数返回的 on 指针或引用取决于实现者,因为它有时很有用。const
  7. TODO:使用以下选项强制执行上述某些操作:clang-tidy
  8. https://clang.llvm.org/extra/clang-tidy/checks/readability-avoid-const-params-in-decls.html
  9. https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

下面是一些代码示例来演示上述规则:const

const 参数示例:
(有些是从这里借来的
)

void f(const std::string);   // Bad: const is top level.
void f(const std::string&);  // Good: const is not top level.

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

const 返回类型示例:
(有些是从这里借来的
)

// BAD--do not do this:
const int foo();
const Clazz foo();
Clazz *const foo();

// OK--up to the implementer:
const int* foo();
const int& foo();
const Clazz* foo();

[复制/粘贴结束]

关键词:常量在函数参数中的应用;编码标准;C 和 C++ 编码标准;编码指南;最佳做法;代码标准;const 返回值